Merge "Handling configuration changes at runtime instead of killing the process" into ub-launcher3-master
diff --git a/Android.bp b/Android.bp
index 2608280..4b32702 100644
--- a/Android.bp
+++ b/Android.bp
@@ -28,3 +28,23 @@
],
platform_apis: true,
}
+
+
+android_library {
+ name: "icon-loader",
+ sdk_version: "28",
+ static_libs: [
+ "androidx.core_core",
+ ],
+ resource_dirs: [
+ "res",
+ ],
+ srcs: [
+ "src/com/android/launcher3/icons/BaseIconFactory.java",
+ "src/com/android/launcher3/icons/BitmapInfo.java",
+ "src/com/android/launcher3/icons/IconNormalizer.java",
+ "src/com/android/launcher3/icons/FixedScaleDrawable.java",
+ "src/com/android/launcher3/icons/ShadowGenerator.java",
+ "src/com/android/launcher3/icons/ColorExtractor.java",
+ ],
+}
diff --git a/Android.mk b/Android.mk
index 15daf1f..fbe19b0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -66,7 +66,8 @@
LOCAL_STATIC_ANDROID_LIBRARIES := \
androidx.recyclerview_recyclerview \
- androidx.dynamicanimation_dynamicanimation
+ androidx.dynamicanimation_dynamicanimation \
+ androidx.preference_preference
LOCAL_STATIC_JAVA_LIBRARIES := LauncherPluginLib
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 8f4d5be..1beaea5 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -122,7 +122,7 @@
android:value="true" />
<activity android:name="com.android.launcher3.dragndrop.AddItemActivity"
- android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.Alert"
+ android:theme="@style/AppItemActivityTheme"
android:excludeFromRecents="true"
android:autoRemoveFromRecents="true"
android:label="@string/action_add_to_workspace" >
@@ -156,7 +156,7 @@
The settings activity. To extend point settings_fragment_name to appropriate fragment class
-->
<activity
- android:name="com.android.launcher3.SettingsActivity"
+ android:name="com.android.launcher3.settings.SettingsActivity"
android:label="@string/settings_button_text"
android:theme="@android:style/Theme.DeviceDefault.Settings"
android:autoRemoveFromRecents="true">
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 41dd0bd..b3ed365 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -112,6 +112,7 @@
CANCEL_TARGET = 14;
TASK_PREVIEW = 15;
SPLIT_SCREEN_TARGET = 16;
+ REMOTE_ACTION_SHORTCUT = 17;
}
enum TipType {
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 7289516..59a937f 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -20,21 +20,34 @@
import android.graphics.Matrix;
import android.view.View;
+import androidx.annotation.AnyThread;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
-import androidx.annotation.AnyThread;
+import java.util.ArrayList;
+import java.util.List;
/**
* Factory class to create and add an overlays on the TaskView
*/
public class TaskOverlayFactory implements ResourceBasedOverride {
-
private static TaskOverlayFactory sInstance;
+ /** Note that these will be shown in order from top to bottom, if available for the task. */
+ private static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[]{
+ new TaskSystemShortcut.AppInfo(),
+ new TaskSystemShortcut.SplitScreen(),
+ new TaskSystemShortcut.Pin(),
+ new TaskSystemShortcut.Install(),
+ };
+
public static TaskOverlayFactory get(Context context) {
Preconditions.assertUIThread();
if (sInstance == null) {
@@ -55,9 +68,23 @@
public static class TaskOverlay {
- public void setTaskInfo(Task task, ThumbnailData thumbnail, Matrix matrix) { }
+ public void setTaskInfo(Task task, ThumbnailData thumbnail, Matrix matrix) {
+ }
- public void reset() { }
+ public void reset() {
+ }
+ public List<TaskSystemShortcut> getEnabledShortcuts(TaskView taskView) {
+ final ArrayList<TaskSystemShortcut> shortcuts = new ArrayList<>();
+ final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
+ for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
+ View.OnClickListener onClickListener =
+ menuOption.getOnClickListener(activity, taskView);
+ if (onClickListener != null) {
+ shortcuts.add(menuOption);
+ }
+ }
+ return shortcuts;
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
index 66ce4c3..a8eb321 100644
--- a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
@@ -63,7 +63,7 @@
protected T mSystemShortcut;
- protected TaskSystemShortcut(T systemShortcut) {
+ public TaskSystemShortcut(T systemShortcut) {
super(systemShortcut);
mSystemShortcut = systemShortcut;
}
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index c4c660c..1c79f44 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -540,7 +540,7 @@
public void updateDisplacement(float displacement) {
// We are moving in the negative x/y direction
displacement = -displacement;
- if (displacement > mTransitionDragLength) {
+ if (displacement > mTransitionDragLength && mTransitionDragLength > 0) {
mCurrentShift.updateValue(1);
if (!mBgLongSwipeMode) {
@@ -815,8 +815,12 @@
long startMillis = SystemClock.uptimeMillis();
executeOnUiThread(() -> {
// Animate the launcher components at the same time as the window, always on UI thread.
- if (mLauncherTransitionController != null && !mWasLauncherAlreadyVisible
- && start != end && duration > 0) {
+ if (mLauncherTransitionController == null) {
+ return;
+ }
+ if (start == end || duration <= 0) {
+ mLauncherTransitionController.getAnimationPlayer().end();
+ } else {
// Adjust start progress and duration in case we are on a different thread.
long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
elapsedMillis = Utilities.boundToRange(elapsedMillis, 0, duration);
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index c4afad7..667165b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -43,6 +43,8 @@
import com.android.quickstep.TaskUtils;
import com.android.quickstep.views.IconView.OnScaleUpdateListener;
+import java.util.List;
+
/**
* Contains options for a recent task when long-pressing its icon.
*/
@@ -50,14 +52,6 @@
private static final Rect sTempRect = new Rect();
- /** Note that these will be shown in order from top to bottom, if available for the task. */
- public static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[] {
- new TaskSystemShortcut.AppInfo(),
- new TaskSystemShortcut.SplitScreen(),
- new TaskSystemShortcut.Pin(),
- new TaskSystemShortcut.Install(),
- };
-
private final OnScaleUpdateListener mTaskViewIconScaleListener = new OnScaleUpdateListener() {
@Override
public void onScaleUpdate(float scale) {
@@ -197,11 +191,13 @@
params.topMargin = (int) -mThumbnailTopMargin;
mTaskIcon.setLayoutParams(params);
- for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
- OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, taskView);
- if (onClickListener != null) {
- addMenuOption(menuOption, onClickListener);
- }
+ final BaseDraggingActivity activity = BaseDraggingActivity.fromContext(getContext());
+ final List<TaskSystemShortcut> shortcuts =
+ taskView.getTaskOverlay().getEnabledShortcuts(taskView);
+ final int count = shortcuts.size();
+ for (int i = 0; i < count; ++i) {
+ final TaskSystemShortcut menuOption = shortcuts.get(i);
+ addMenuOption(menuOption, menuOption.getOnClickListener(activity, taskView));
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 7223f97..ce65de1 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -173,6 +173,10 @@
return 0;
}
+ public TaskOverlay getTaskOverlay() {
+ return mOverlay;
+ }
+
@Override
protected void onDraw(Canvas canvas) {
drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius);
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index c1424c4..56074f0 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -42,12 +42,12 @@
import android.widget.FrameLayout;
import android.widget.Toast;
-import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskSystemShortcut;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.views.RecentsView.PageCallbacks;
@@ -56,8 +56,9 @@
import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
-
import com.android.systemui.shared.system.ActivityOptionsCompat;
+
+import java.util.List;
import java.util.function.Consumer;
/**
@@ -190,6 +191,10 @@
return mIconView;
}
+ public TaskOverlayFactory.TaskOverlay getTaskOverlay() {
+ return mSnapshotView.getTaskOverlay();
+ }
+
public void launchTask(boolean animate) {
launchTask(animate, (result) -> {
if (!result) {
@@ -384,7 +389,11 @@
final Context context = getContext();
final BaseDraggingActivity activity = fromContext(context);
- for (TaskSystemShortcut menuOption : TaskMenuView.MENU_OPTIONS) {
+ final List<TaskSystemShortcut> shortcuts =
+ mSnapshotView.getTaskOverlay().getEnabledShortcuts(this);
+ final int count = shortcuts.size();
+ for (int i = 0; i < count; ++i) {
+ final TaskSystemShortcut menuOption = shortcuts.get(i);
OnClickListener onClickListener = menuOption.getOnClickListener(activity, this);
if (onClickListener != null) {
info.addAction(menuOption.createAccessibilityAction(context));
@@ -407,7 +416,11 @@
return true;
}
- for (TaskSystemShortcut menuOption : TaskMenuView.MENU_OPTIONS) {
+ final List<TaskSystemShortcut> shortcuts =
+ mSnapshotView.getTaskOverlay().getEnabledShortcuts(this);
+ final int count = shortcuts.size();
+ for (int i = 0; i < count; ++i) {
+ final TaskSystemShortcut menuOption = shortcuts.get(i);
if (menuOption.hasHandlerForAction(action)) {
OnClickListener onClickListener = menuOption.getOnClickListener(
fromContext(getContext()), this);
diff --git a/res/layout/add_item_confirmation_activity.xml b/res/layout/add_item_confirmation_activity.xml
index 6c316e6..830255b 100644
--- a/res/layout/add_item_confirmation_activity.xml
+++ b/res/layout/add_item_confirmation_activity.xml
@@ -44,7 +44,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/colorPrimaryDark"
- android:theme="@style/WidgetContainerTheme">
+ android:theme="?attr/widgetsTheme">
<com.android.launcher3.dragndrop.LivePreviewWidgetCell
android:id="@+id/widget_cell"
diff --git a/res/values-v19/styles.xml b/res/values-night-v26/styles.xml
similarity index 70%
copy from res/values-v19/styles.xml
copy to res/values-night-v26/styles.xml
index 36c0971..510e1f4 100644
--- a/res/values-v19/styles.xml
+++ b/res/values-night-v26/styles.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
-* Copyright (C) 2016 The Android Open Source Project
+* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,11 @@
* limitations under the License.
*/
-->
+
<resources>
- <style name="LauncherTheme" parent="@style/BaseLauncherThemeWithCustomAttrs">
- <item name="android:windowTranslucentStatus">true</item>
- <item name="android:windowTranslucentNavigation">true</item>
+ <style name="AppItemActivityTheme" parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
+ <item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
</style>
</resources>
\ No newline at end of file
diff --git a/res/values-v21/styles.xml b/res/values-v21/styles.xml
deleted file mode 100644
index 927719c..0000000
--- a/res/values-v21/styles.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-* Copyright (C) 2016 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
--->
-<resources>
-
- <style name="LauncherTheme" parent="@style/BaseLauncherThemeWithCustomAttrs">
- <item name="android:windowTranslucentStatus">false</item>
- <item name="android:windowTranslucentNavigation">false</item>
- <item name="android:windowDrawsSystemBarBackgrounds">true</item>
- <item name="android:statusBarColor">#00000000</item>
- <item name="android:navigationBarColor">#00000000</item>
- </style>
-</resources>
diff --git a/res/values-v19/styles.xml b/res/values-v22/styles.xml
similarity index 71%
rename from res/values-v19/styles.xml
rename to res/values-v22/styles.xml
index 36c0971..f86db7a 100644
--- a/res/values-v19/styles.xml
+++ b/res/values-v22/styles.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
-* Copyright (C) 2016 The Android Open Source Project
+* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,11 @@
* limitations under the License.
*/
-->
+
<resources>
- <style name="LauncherTheme" parent="@style/BaseLauncherThemeWithCustomAttrs">
- <item name="android:windowTranslucentStatus">true</item>
- <item name="android:windowTranslucentNavigation">true</item>
+ <style name="AppItemActivityTheme" parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
+ <item name="widgetsTheme">@style/WidgetContainerTheme</item>
</style>
</resources>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index 0efaccf..85c2e65 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -123,6 +123,7 @@
<item type="id" name="action_deep_shortcuts" />
<item type="id" name="action_shortcuts_and_notifications"/>
<item type="id" name="action_dismiss_notification" />
+ <item type="id" name="action_remote_action_shortcut" />
<!-- QSB IDs. DO not change -->
<item type="id" name="search_container_workspace" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index eb6b284..7e5784d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -347,4 +347,7 @@
<string name="work_mode_off_label">Notifications and apps are off</string>
<string name="bottom_work_tab_user_education_close_button">Close</string>
<string name="bottom_work_tab_user_education_closed">Closed</string>
+
+ <!-- Failed action error message: e.g. Failed: Pause -->
+ <string name="remote_action_failed">Failed: <xliff:g id="what" example="Pause">%1$s</xliff:g></string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 098aac5..7b11891 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -27,7 +27,7 @@
<item name="android:colorEdgeEffect">#FF757575</item>
</style>
- <style name="BaseLauncherThemeWithCustomAttrs" parent="@style/BaseLauncherTheme">
+ <style name="LauncherTheme" parent="@style/BaseLauncherTheme">
<item name="allAppsScrimColor">#EAFFFFFF</item>
<item name="allAppsInterimScrimAlpha">46</item>
<item name="allAppsNavBarScrimColor">#66FFFFFF</item>
@@ -44,9 +44,13 @@
<item name="widgetsTheme">@style/WidgetContainerTheme</item>
<item name="folderBadgeColor">?android:attr/colorPrimary</item>
<item name="loadingIconColor">#FFF</item>
- </style>
- <style name="LauncherTheme" parent="@style/BaseLauncherThemeWithCustomAttrs"></style>
+ <item name="android:windowTranslucentStatus">false</item>
+ <item name="android:windowTranslucentNavigation">false</item>
+ <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+ <item name="android:statusBarColor">#00000000</item>
+ <item name="android:navigationBarColor">#00000000</item>
+ </style>
<style name="LauncherTheme.DarkText" parent="@style/LauncherTheme">
<item name="workspaceTextColor">#FF212121</item>
@@ -94,6 +98,10 @@
<style name="AppTheme.Dark" parent="@style/LauncherTheme.Dark" />
<style name="AppTheme.Dark.DarkText" parent="@style/LauncherTheme.Dark.DarkText" />
+ <style name="AppItemActivityTheme" parent="@android:style/Theme.Material.Light.Dialog.Alert">
+ <item name="widgetsTheme">@style/WidgetContainerTheme</item>
+ </style>
+
<!--
Theme overrides to element on homescreen, i.e., which are drawn on top on wallpaper.
Various foreground colors are overridden to be workspaceTextColor so that they are properly
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 1df7c2f..c55cc49 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -14,9 +14,10 @@
limitations under the License.
-->
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+<androidx.preference.PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android">
- <com.android.launcher3.views.ButtonPreference
+ <com.android.launcher3.settings.IconBadgingPreference
android:key="pref_icon_badging"
android:title="@string/icon_badging_title"
android:persistent="false"
@@ -27,7 +28,7 @@
android:name=":settings:fragment_args_key"
android:value="notification_badging" />
</intent>
- </com.android.launcher3.views.ButtonPreference>
+ </com.android.launcher3.settings.IconBadgingPreference>
<SwitchPreference
android:key="pref_add_icon_to_home"
@@ -52,10 +53,10 @@
android:defaultValue=""
android:persistent="false" />
- <PreferenceScreen
+ <androidx.preference.PreferenceScreen
android:fragment="com.android.launcher3.config.FlagTogglerPreferenceFragment"
android:key="flag_toggler"
android:persistent="false"
android:title="Feature flags"/>
-</PreferenceScreen>
+</androidx.preference.PreferenceScreen>
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 7190f12..851454b 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -480,7 +480,7 @@
final LauncherAppState app = LauncherAppState.getInstance(mContext);
// Set default values until proper values is loaded.
appInfo.title = "";
- app.getIconCache().getDefaultIcon(user).applyTo(appInfo);
+ appInfo.applyFrom(app.getIconCache().getDefaultIcon(user));
final ShortcutInfo si = appInfo.makeShortcut();
if (Looper.myLooper() == LauncherModel.getWorkerLooper()) {
app.getIconCache().getTitleAndIcon(si, activityInfo, false /* useLowResIcon */);
@@ -495,7 +495,7 @@
} else if (shortcutInfo != null) {
ShortcutInfo si = new ShortcutInfo(shortcutInfo, mContext);
LauncherIcons li = LauncherIcons.obtain(mContext);
- li.createShortcutIcon(shortcutInfo).applyTo(si);
+ si.applyFrom(li.createShortcutIcon(shortcutInfo));
li.recycle();
return Pair.create((ItemInfo) si, (Object) shortcutInfo);
} else if (providerInfo != null) {
@@ -656,7 +656,7 @@
if (iconInfo == null) {
iconInfo = app.getIconCache().getDefaultIcon(info.user);
}
- iconInfo.applyTo(info);
+ info.applyFrom(iconInfo);
info.title = Utilities.trim(name);
info.contentDescription = UserManagerCompat.getInstance(app.getContext())
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
index 6d453c9..e29f927 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -20,6 +20,8 @@
import android.graphics.Bitmap;
+import com.android.launcher3.icons.BitmapInfo;
+
/**
* Represents an ItemInfo which also holds an icon.
*/
@@ -118,4 +120,10 @@
public boolean usingLowResIcon() {
return iconBitmap == LOW_RES_ICON;
}
+
+ public void applyFrom(BitmapInfo info) {
+ iconBitmap = info.icon;
+ iconColor = info.color;
+ }
+
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 0395fbb..e714a0b 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -110,7 +110,6 @@
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.PackageManagerHelper;
@@ -137,6 +136,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -2163,11 +2163,11 @@
}
/**
- * Copies LauncherModel's map of activities to shortcut ids to Launcher's. This is necessary
+ * Copies LauncherModel's map of activities to shortcut counts to Launcher's. This is necessary
* because LauncherModel's map is updated in the background, while Launcher runs on the UI.
*/
@Override
- public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) {
+ public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) {
mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index cbfde25..b3dabae 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -57,7 +57,6 @@
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Provider;
@@ -69,6 +68,7 @@
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -161,7 +161,7 @@
public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets);
public void onPageBoundSynchronously(int page);
public void executeOnNextDraw(ViewOnDrawExecutor executor);
- public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
+ public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
}
LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
@@ -657,7 +657,7 @@
updateAndBindShortcutInfo(() -> {
si.updateFromDeepShortcutInfo(info, mApp.getContext());
LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
- li.createShortcutIcon(info).applyTo(si);
+ si.applyFrom(li.createShortcutIcon(info));
li.recycle();
return si;
});
diff --git a/src/com/android/launcher3/MainProcessInitializer.java b/src/com/android/launcher3/MainProcessInitializer.java
index a18dfde..a253893 100644
--- a/src/com/android/launcher3/MainProcessInitializer.java
+++ b/src/com/android/launcher3/MainProcessInitializer.java
@@ -19,6 +19,7 @@
import android.content.Context;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.folder.FolderShape;
import com.android.launcher3.graphics.IconShapeOverride;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.ResourceBasedOverride;
@@ -39,5 +40,6 @@
FeatureFlags.initialize(context);
IconShapeOverride.apply(context);
SessionCommitReceiver.applyDefaultUserPrefs(context);
+ FolderShape.init();
}
}
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
deleted file mode 100644
index a17f614..0000000
--- a/src/com/android/launcher3/SettingsActivity.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
-import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
-import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
-
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.Fragment;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Bundle;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceScreen;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.view.View;
-import android.widget.Adapter;
-import android.widget.ListView;
-
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.IconShapeOverride;
-import com.android.launcher3.notification.NotificationListener;
-import com.android.launcher3.util.ListViewHighlighter;
-import com.android.launcher3.util.SecureSettingsObserver;
-import com.android.launcher3.views.ButtonPreference;
-
-import java.util.Objects;
-
-/**
- * Settings activity for Launcher. Currently implements the following setting: Allow rotation
- */
-public class SettingsActivity extends Activity
- implements PreferenceFragment.OnPreferenceStartFragmentCallback {
-
- private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
-
- private static final String ICON_BADGING_PREFERENCE_KEY = "pref_icon_badging";
- /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
- private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
-
- private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
- private static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
- private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
- private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- if (savedInstanceState == null) {
- Fragment f = Fragment.instantiate(this, getString(R.string.settings_fragment_name));
- // Display the fragment as the main content.
- getFragmentManager().beginTransaction()
- .replace(android.R.id.content, f)
- .commit();
- }
- }
-
- protected PreferenceFragment getNewFragment() {
- return new LauncherSettingsFragment();
- }
-
- @Override
- public boolean onPreferenceStartFragment(
- PreferenceFragment preferenceFragment, Preference pref) {
- if (getFragmentManager().isStateSaved()) {
- // Sometimes onClick can come after onPause because of being posted on the handler.
- // Skip starting new fragments in that case.
- return false;
- }
- Fragment f = Fragment.instantiate(this, pref.getFragment(), pref.getExtras());
- if (f instanceof DialogFragment) {
- ((DialogFragment) f).show(getFragmentManager(), pref.getKey());
- } else {
- getFragmentManager()
- .beginTransaction()
- .replace(android.R.id.content, f)
- .addToBackStack(pref.getKey())
- .commit();
- }
- return true;
- }
-
- /**
- * This fragment shows the launcher preferences.
- */
- public static class LauncherSettingsFragment extends PreferenceFragment {
-
- private SecureSettingsObserver mIconBadgingObserver;
-
- private String mPreferenceKey;
- private boolean mPreferenceHighlighted = false;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null) {
- mPreferenceHighlighted = savedInstanceState.getBoolean(SAVE_HIGHLIGHTED_KEY);
- }
-
- getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
- addPreferencesFromResource(R.xml.launcher_preferences);
-
- // Only show flag toggler UI if this build variant implements that.
- Preference flagToggler = findPreference(FLAGS_PREFERENCE_KEY);
- if (flagToggler != null && !FeatureFlags.showFlagTogglerUi()) {
- getPreferenceScreen().removePreference(flagToggler);
- }
-
- ContentResolver resolver = getActivity().getContentResolver();
-
- ButtonPreference iconBadgingPref =
- (ButtonPreference) findPreference(ICON_BADGING_PREFERENCE_KEY);
- if (!Utilities.ATLEAST_OREO) {
- getPreferenceScreen().removePreference(
- findPreference(SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY));
- getPreferenceScreen().removePreference(iconBadgingPref);
- } else if (!getResources().getBoolean(R.bool.notification_badging_enabled)) {
- getPreferenceScreen().removePreference(iconBadgingPref);
- } else {
- // Listen to system notification badge settings while this UI is active.
- mIconBadgingObserver = newNotificationSettingsObserver(
- getActivity(), new IconBadgingObserver(iconBadgingPref, resolver));
- mIconBadgingObserver.register();
- // Also listen if notification permission changes
- mIconBadgingObserver.getResolver().registerContentObserver(
- Settings.Secure.getUriFor(NOTIFICATION_ENABLED_LISTENERS), false,
- mIconBadgingObserver);
- mIconBadgingObserver.dispatchOnChange();
- }
-
- Preference iconShapeOverride = findPreference(IconShapeOverride.KEY_PREFERENCE);
- if (iconShapeOverride != null) {
- if (IconShapeOverride.isSupported(getActivity())) {
- IconShapeOverride.handlePreferenceUi((ListPreference) iconShapeOverride);
- } else {
- getPreferenceScreen().removePreference(iconShapeOverride);
- }
- }
-
- // Setup allow rotation preference
- Preference rotationPref = findPreference(ALLOW_ROTATION_PREFERENCE_KEY);
- if (getResources().getBoolean(R.bool.allow_rotation)) {
- // Launcher supports rotation by default. No need to show this setting.
- getPreferenceScreen().removePreference(rotationPref);
- } else {
- // Initialize the UI once
- rotationPref.setDefaultValue(getAllowRotationDefaultValue());
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- Intent intent = getActivity().getIntent();
- mPreferenceKey = intent.getStringExtra(EXTRA_FRAGMENT_ARG_KEY);
- if (isAdded() && !mPreferenceHighlighted && !TextUtils.isEmpty(mPreferenceKey)) {
- getView().postDelayed(this::highlightPreference, DELAY_HIGHLIGHT_DURATION_MILLIS);
- }
- }
-
- private void highlightPreference() {
- Preference pref = findPreference(mPreferenceKey);
- if (pref == null || getPreferenceScreen() == null) {
- return;
- }
- PreferenceScreen screen = getPreferenceScreen();
- if (Utilities.ATLEAST_OREO) {
- screen = selectPreferenceRecursive(pref, screen);
- }
- if (screen == null) {
- return;
- }
-
- View root = screen.getDialog() != null
- ? screen.getDialog().getWindow().getDecorView() : getView();
- ListView list = root.findViewById(android.R.id.list);
- if (list == null || list.getAdapter() == null) {
- return;
- }
- Adapter adapter = list.getAdapter();
-
- // Find the position
- int position = -1;
- for (int i = adapter.getCount() - 1; i >= 0; i--) {
- if (pref == adapter.getItem(i)) {
- position = i;
- break;
- }
- }
- new ListViewHighlighter(list, position);
- mPreferenceHighlighted = true;
- }
-
- @Override
- public void onDestroy() {
- if (mIconBadgingObserver != null) {
- mIconBadgingObserver.unregister();
- mIconBadgingObserver = null;
- }
- super.onDestroy();
- }
-
- @TargetApi(Build.VERSION_CODES.O)
- private PreferenceScreen selectPreferenceRecursive(
- Preference pref, PreferenceScreen topParent) {
- if (!(pref.getParent() instanceof PreferenceScreen)) {
- return null;
- }
-
- PreferenceScreen parent = (PreferenceScreen) pref.getParent();
- if (Objects.equals(parent.getKey(), topParent.getKey())) {
- return parent;
- } else if (selectPreferenceRecursive(parent, topParent) != null) {
- ((PreferenceScreen) parent.getParent())
- .onItemClick(null, null, parent.getOrder(), 0);
- return parent;
- } else {
- return null;
- }
- }
- }
-
- /**
- * Content observer which listens for system badging setting changes,
- * and updates the launcher badging setting subtext accordingly.
- */
- private static class IconBadgingObserver implements SecureSettingsObserver.OnChangeListener {
-
- private final ButtonPreference mBadgingPref;
- private final ContentResolver mResolver;
-
- public IconBadgingObserver(ButtonPreference badgingPref, ContentResolver resolver) {
- mBadgingPref = badgingPref;
- mResolver = resolver;
- }
-
- @Override
- public void onSettingsChanged(boolean enabled) {
- int summary = enabled ? R.string.icon_badging_desc_on : R.string.icon_badging_desc_off;
-
- boolean serviceEnabled = true;
- if (enabled) {
- // Check if the listener is enabled or not.
- String enabledListeners =
- Settings.Secure.getString(mResolver, NOTIFICATION_ENABLED_LISTENERS);
- ComponentName myListener =
- new ComponentName(mBadgingPref.getContext(), NotificationListener.class);
- serviceEnabled = enabledListeners != null &&
- (enabledListeners.contains(myListener.flattenToString()) ||
- enabledListeners.contains(myListener.flattenToShortString()));
- if (!serviceEnabled) {
- summary = R.string.title_missing_notification_access;
- }
- }
- mBadgingPref.setWidgetFrameVisible(!serviceEnabled);
- mBadgingPref.setFragment(
- serviceEnabled ? null : NotificationAccessConfirmation.class.getName());
- mBadgingPref.setSummary(summary);
-
- }
- }
-
- public static class NotificationAccessConfirmation
- extends DialogFragment implements DialogInterface.OnClickListener {
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final Context context = getActivity();
- String msg = context.getString(R.string.msg_missing_notification_access,
- context.getString(R.string.derived_app_name));
- return new AlertDialog.Builder(context)
- .setTitle(R.string.title_missing_notification_access)
- .setMessage(msg)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(R.string.title_change_settings, this)
- .create();
- }
-
- @Override
- public void onClick(DialogInterface dialogInterface, int i) {
- ComponentName cn = new ComponentName(getActivity(), NotificationListener.class);
- Bundle showFragmentArgs = new Bundle();
- showFragmentArgs.putString(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString());
-
- Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .putExtra(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString())
- .putExtra(EXTRA_SHOW_FRAGMENT_ARGS, showFragmentArgs);
- getActivity().startActivity(intent);
- }
- }
-}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 11e601c..3f7d68d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -127,8 +127,8 @@
private static final int DEFAULT_PAGE = 0;
- private static final boolean MAP_NO_RECURSE = false;
- private static final boolean MAP_RECURSE = true;
+ public static final boolean MAP_NO_RECURSE = false;
+ public static final boolean MAP_RECURSE = true;
// The screen id used for the empty screen always present to the right.
public static final int EXTRA_EMPTY_SCREEN_ID = -201;
@@ -3121,7 +3121,7 @@
* @param recurse true: iterate over folder children. false: op get the folders themselves.
* @param op the operator to map over the shortcuts
*/
- void mapOverItems(boolean recurse, ItemOperator op) {
+ public void mapOverItems(boolean recurse, ItemOperator op) {
ArrayList<ShortcutAndWidgetContainer> containers = getAllShortcutAndWidgetContainers();
final int containerCount = containers.size();
for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) {
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
index fb44660..82617fe 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
@@ -139,7 +139,7 @@
ShortcutInfo info = new ShortcutInfo(compat, context);
// Apply the unbadged icon and fetch the actual icon asynchronously.
LauncherIcons li = LauncherIcons.obtain(context);
- li.createShortcutIcon(compat, false /* badged */).applyTo(info);
+ info.applyFrom(li.createShortcutIcon(compat, false /* badged */));
li.recycle();
LauncherAppState.getInstance(context).getModel()
.updateAndBindShortcutInfo(info, compat);
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 290d8c4..64b5652 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.SharedPreferences;
+import android.provider.Settings;
import androidx.annotation.GuardedBy;
import androidx.annotation.Keep;
@@ -51,8 +52,10 @@
throw new UnsupportedOperationException("Don't instantiate BaseFlags");
}
- public static boolean showFlagTogglerUi() {
- return Utilities.IS_DEBUG_DEVICE;
+ public static boolean showFlagTogglerUi(Context context) {
+ return Utilities.IS_DEBUG_DEVICE &&
+ Settings.Global.getInt(context.getApplicationContext().getContentResolver(),
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
}
public static final boolean IS_DOGFOOD_BUILD = false;
@@ -91,8 +94,8 @@
"APPLY_CONFIG_AT_RUNTIME", false, "Apply display changes dynamically");
public static void initialize(Context context) {
- // Avoid the disk read for builds without the flags UI.
- if (showFlagTogglerUi()) {
+ // Avoid the disk read for user builds
+ if (Utilities.IS_DEBUG_DEVICE) {
SharedPreferences sharedPreferences =
context.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE);
synchronized (sLock) {
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 6d1efd5..8f223a3 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -204,7 +204,7 @@
public void run() {
LauncherAppState appState = LauncherAppState.getInstance(mLauncher);
Object[] outObj = new Object[1];
- final Drawable dr = getFullDrawable(info, appState, outObj);
+ Drawable dr = getFullDrawable(info, appState, outObj);
if (dr instanceof AdaptiveIconDrawable) {
int w = mBitmap.getWidth();
@@ -220,10 +220,20 @@
mBadge = getBadge(info, appState, outObj[0]);
mBadge.setBounds(badgeBounds);
- LauncherIcons li = LauncherIcons.obtain(mLauncher);
- Utilities.scaleRectAboutCenter(bounds,
- li.getNormalizer().getScale(dr, null, null, null));
- li.recycle();
+ // Do not draw the background in case of folder as its translucent
+ mDrawBitmap = !(dr instanceof FolderAdaptiveIcon);
+
+ try (LauncherIcons li = LauncherIcons.obtain(mLauncher)) {
+ Drawable nDr; // drawable to be normalized
+ if (mDrawBitmap) {
+ nDr = dr;
+ } else {
+ // Since we just want the scale, avoid heavy drawing operations
+ nDr = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null);
+ }
+ Utilities.scaleRectAboutCenter(bounds,
+ li.getNormalizer().getScale(nDr, null));
+ }
AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) dr;
// Shrink very tiny bit so that the clip path is smaller than the original bitmap
@@ -259,9 +269,6 @@
// Assign the variable on the UI thread to avoid race conditions.
mScaledMaskPath = mask;
- // Do not draw the background in case of folder as its translucent
- mDrawBitmap = !(dr instanceof FolderAdaptiveIcon);
-
if (info.isDisabled()) {
FastBitmapDrawable d = new FastBitmapDrawable((Bitmap) null);
d.setIsDisabled(true);
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index c4d1058..94c8d45 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -26,6 +26,8 @@
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Path;
import android.graphics.Rect;
import android.text.InputType;
import android.text.Selection;
@@ -151,6 +153,8 @@
// Cell ranks used for drag and drop
@Thunk int mTargetRank, mPrevTargetRank, mEmptyCellRank;
+ private Path mClipPath;
+
@ViewDebug.ExportedProperty(category = "launcher",
mapping = {
@ViewDebug.IntToString(from = STATE_NONE, to = "STATE_NONE"),
@@ -1476,4 +1480,25 @@
sHintText = res.getString(R.string.folder_hint_text);
}
}
+
+ /**
+ * Alternative to using {@link #getClipToOutline()} as it only works with derivatives of
+ * rounded rect.
+ */
+ public void setClipPath(Path clipPath) {
+ mClipPath = clipPath;
+ invalidate();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mClipPath != null) {
+ int count = canvas.save();
+ canvas.clipPath(mClipPath);
+ super.draw(canvas);
+ canvas.restoreToCount(count);
+ } else {
+ super.draw(canvas);
+ }
+ }
}
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 1277a20..fa890b9 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.BubbleTextView.TEXT_ALPHA_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+import static com.android.launcher3.folder.FolderShape.getShape;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -40,7 +41,6 @@
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PropertyResetListener;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.util.Themes;
@@ -166,7 +166,6 @@
Math.round((totalOffsetX + initialSize) / initialScale),
Math.round((paddingOffsetY + initialSize) / initialScale));
Rect endRect = new Rect(0, 0, lp.width, lp.height);
- float initialRadius = initialSize / initialScale / 2f;
float finalRadius = Utilities.pxFromDp(2, mContext.getResources().getDisplayMetrics());
// Create the animators.
@@ -189,14 +188,8 @@
play(a, getAnimator(mFolder, SCALE_PROPERTY, initialScale, finalScale));
play(a, getAnimator(mFolderBackground, "color", initialColor, finalColor));
play(a, mFolderIcon.mFolderName.createTextAlphaAnimator(!mIsOpening));
- RoundedRectRevealOutlineProvider outlineProvider = new RoundedRectRevealOutlineProvider(
- initialRadius, finalRadius, startRect, endRect) {
- @Override
- public boolean shouldRemoveElevationDuringAnimation() {
- return true;
- }
- };
- play(a, outlineProvider.createRevealAnimator(mFolder, !mIsOpening));
+ play(a, getShape().createRevealAnimator(
+ mFolder, startRect, endRect, finalRadius, !mIsOpening));
// Animate the elevation midway so that the shadow is not noticeable in the background.
int midDuration = mDuration / 2;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index d09f036..429d44f 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -75,6 +75,7 @@
* An icon that can appear on in the workspace representing an {@link Folder}.
*/
public class FolderIcon extends FrameLayout implements FolderListener {
+
@Thunk Launcher mLauncher;
@Thunk Folder mFolder;
private FolderInfo mInfo;
@@ -477,20 +478,9 @@
if (mFolder == null) return;
if (mFolder.getItemCount() == 0 && !mAnimating) return;
- final int saveCount;
-
- if (canvas.isHardwareAccelerated()) {
- saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
- } else {
- saveCount = canvas.save();
- canvas.clipPath(mBackground.getClipPath());
- }
-
+ final int saveCount = canvas.save();
+ canvas.clipPath(mBackground.getClipPath());
mPreviewItemManager.draw(canvas);
-
- if (canvas.isHardwareAccelerated()) {
- mBackground.clipCanvasHardware(canvas);
- }
canvas.restoreToCount(saveCount);
if (!mBackground.drawingDelegated()) {
diff --git a/src/com/android/launcher3/folder/FolderShape.java b/src/com/android/launcher3/folder/FolderShape.java
new file mode 100644
index 0000000..ae279cb
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderShape.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.folder;
+
+import static com.android.launcher3.Workspace.MAP_NO_RECURSE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.FloatArrayEvaluator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.TargetApi;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.Region.Op;
+import android.graphics.RegionIterator;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
+import android.view.ViewOutlineProvider;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+
+/**
+ * Abstract representation of the shape of a folder icon
+ */
+public abstract class FolderShape {
+
+ private static FolderShape sInstance = new Circle();
+
+ public static FolderShape getShape() {
+ return sInstance;
+ }
+
+ private static FolderShape[] getAllShapes() {
+ return new FolderShape[] {
+ new Circle(),
+ new RoundedSquare(8f / 50), // Ratios based on path defined in config_icon_mask
+ new RoundedSquare(30f / 50),
+ new Square(),
+ new TearDrop(),
+ new Squircle()};
+ }
+
+ public abstract void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,
+ Paint paint);
+
+ public abstract void addShape(Path path, float offsetX, float offsetY, float radius);
+
+ public abstract Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect,
+ float endRadius, boolean isReversed);
+
+ /**
+ * Abstract shape where the reveal animation is a derivative of a round rect animation
+ */
+ private static abstract class SimpleRectShape extends FolderShape {
+
+ @Override
+ public final Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect,
+ float endRadius, boolean isReversed) {
+ return new RoundedRectRevealOutlineProvider(
+ getStartRadius(startRect), endRadius, startRect, endRect) {
+ @Override
+ public boolean shouldRemoveElevationDuringAnimation() {
+ return true;
+ }
+ }.createRevealAnimator(target, isReversed);
+ }
+
+ protected abstract float getStartRadius(Rect startRect);
+ }
+
+ /**
+ * Abstract shape which draws using {@link Path}
+ */
+ private static abstract class PathShape extends FolderShape {
+
+ private final Path mTmpPath = new Path();
+
+ @Override
+ public final void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,
+ Paint paint) {
+ mTmpPath.reset();
+ addShape(mTmpPath, offsetX, offsetY, radius);
+ canvas.drawPath(mTmpPath, paint);
+ }
+
+ protected abstract AnimatorUpdateListener newUpdateListener(
+ Rect startRect, Rect endRect, float endRadius, Path outPath);
+
+ @Override
+ public final Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect,
+ float endRadius, boolean isReversed) {
+ Path path = new Path();
+ AnimatorUpdateListener listener =
+ newUpdateListener(startRect, endRect, endRadius, path);
+
+ ValueAnimator va =
+ isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
+ va.addListener(new AnimatorListenerAdapter() {
+ private ViewOutlineProvider mOldOutlineProvider;
+
+ public void onAnimationStart(Animator animation) {
+ mOldOutlineProvider = target.getOutlineProvider();
+ target.setOutlineProvider(null);
+
+ target.setTranslationZ(-target.getElevation());
+ }
+
+ public void onAnimationEnd(Animator animation) {
+ target.setTranslationZ(0);
+ target.setClipPath(null);
+ target.setOutlineProvider(mOldOutlineProvider);
+ }
+ });
+
+ va.addUpdateListener((anim) -> {
+ path.reset();
+ listener.onAnimationUpdate(anim);
+ target.setClipPath(path);
+ });
+
+ return va;
+ }
+ }
+
+ public static final class Circle extends SimpleRectShape {
+
+ @Override
+ public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) {
+ canvas.drawCircle(radius + offsetX, radius + offsetY, radius, p);
+ }
+
+ @Override
+ public void addShape(Path path, float offsetX, float offsetY, float radius) {
+ path.addCircle(radius + offsetX, radius + offsetY, radius, Path.Direction.CW);
+ }
+
+ @Override
+ protected float getStartRadius(Rect startRect) {
+ return startRect.width() / 2f;
+ }
+ }
+
+ public static class Square extends SimpleRectShape {
+
+ @Override
+ public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) {
+ float cx = radius + offsetX;
+ float cy = radius + offsetY;
+ canvas.drawRect(cx - radius, cy - radius, cx + radius, cy + radius, p);
+ }
+
+ @Override
+ public void addShape(Path path, float offsetX, float offsetY, float radius) {
+ float cx = radius + offsetX;
+ float cy = radius + offsetY;
+ path.addRect(cx - radius, cy - radius, cx + radius, cy + radius, Path.Direction.CW);
+ }
+
+ @Override
+ protected float getStartRadius(Rect startRect) {
+ return 0;
+ }
+ }
+
+ public static class RoundedSquare extends SimpleRectShape {
+
+ /**
+ * Ratio of corner radius to half size. Based on the
+ */
+ private final float mRadiusFactor;
+
+ public RoundedSquare(float radiusFactor) {
+ mRadiusFactor = radiusFactor;
+ }
+
+ @Override
+ public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) {
+ float cx = radius + offsetX;
+ float cy = radius + offsetY;
+ float cr = radius * mRadiusFactor;
+ canvas.drawRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr, p);
+ }
+
+ @Override
+ public void addShape(Path path, float offsetX, float offsetY, float radius) {
+ float cx = radius + offsetX;
+ float cy = radius + offsetY;
+ float cr = radius * mRadiusFactor;
+ path.addRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr,
+ Path.Direction.CW);
+ }
+
+ @Override
+ protected float getStartRadius(Rect startRect) {
+ return (startRect.width() / 2f) * mRadiusFactor;
+ }
+ }
+
+ public static class TearDrop extends PathShape {
+
+ /**
+ * Radio of short radius to large radius, based on the shape options defined in the config.
+ */
+ private static final float RADIUS_RATIO = 15f / 50;
+
+ private final float[] mTempRadii = new float[8];
+
+ @Override
+ public void addShape(Path p, float offsetX, float offsetY, float r1) {
+ float r2 = r1 * RADIUS_RATIO;
+ float cx = r1 + offsetX;
+ float cy = r1 + offsetY;
+
+ p.addRoundRect(cx - r1, cy - r1, cx + r1, cy + r1, getRadiiArray(r1, r2),
+ Path.Direction.CW);
+ }
+
+ private float[] getRadiiArray(float r1, float r2) {
+ mTempRadii[0] = mTempRadii [1] = mTempRadii[2] = mTempRadii[3] =
+ mTempRadii[6] = mTempRadii[7] = r1;
+ mTempRadii[4] = mTempRadii[5] = r2;
+ return mTempRadii;
+ }
+
+ @Override
+ protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect,
+ float endRadius, Path outPath) {
+ float r1 = startRect.width() / 2f;
+ float r2 = r1 * RADIUS_RATIO;
+
+ float[] startValues = new float[] {
+ startRect.left, startRect.top, startRect.right, startRect.bottom, r1, r2};
+ float[] endValues = new float[] {
+ endRect.left, endRect.top, endRect.right, endRect.bottom, endRadius, endRadius};
+
+ FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[6]);
+
+ return (anim) -> {
+ float progress = (Float) anim.getAnimatedValue();
+ float[] values = evaluator.evaluate(progress, startValues, endValues);
+ outPath.addRoundRect(
+ values[0], values[1], values[2], values[3],
+ getRadiiArray(values[4], values[5]), Path.Direction.CW);
+ };
+ }
+ }
+
+ public static class Squircle extends PathShape {
+
+ /**
+ * Radio of radius to circle radius, based on the shape options defined in the config.
+ */
+ private static final float RADIUS_RATIO = 10f / 50;
+
+ @Override
+ public void addShape(Path p, float offsetX, float offsetY, float r) {
+ float cx = r + offsetX;
+ float cy = r + offsetY;
+ float control = r - r * RADIUS_RATIO;
+
+ p.moveTo(cx, cy - r);
+ addLeftCurve(cx, cy, r, control, p);
+ addRightCurve(cx, cy, r, control, p);
+ addLeftCurve(cx, cy, -r, -control, p);
+ addRightCurve(cx, cy, -r, -control, p);
+ p.close();
+ }
+
+ private void addLeftCurve(float cx, float cy, float r, float control, Path path) {
+ path.cubicTo(
+ cx - control, cy - r,
+ cx - r, cy - control,
+ cx - r, cy);
+ }
+
+ private void addRightCurve(float cx, float cy, float r, float control, Path path) {
+ path.cubicTo(
+ cx - r, cy + control,
+ cx - control, cy + r,
+ cx, cy + r);
+ }
+
+ @Override
+ protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect,
+ float endR, Path outPath) {
+
+ float startCX = startRect.exactCenterX();
+ float startCY = startRect.exactCenterY();
+ float startR = startRect.width() / 2f;
+ float startControl = startR - startR * RADIUS_RATIO;
+ float startHShift = 0;
+ float startVShift = 0;
+
+ float endCX = endRect.exactCenterX();
+ float endCY = endRect.exactCenterY();
+ // Approximate corner circle using bezier curves
+ // http://spencermortensen.com/articles/bezier-circle/
+ float endControl = endR * 0.551915024494f;
+ float endHShift = endRect.width() / 2f - endR;
+ float endVShift = endRect.height() / 2f - endR;
+
+ return (anim) -> {
+ float progress = (Float) anim.getAnimatedValue();
+
+ float cx = (1 - progress) * startCX + progress * endCX;
+ float cy = (1 - progress) * startCY + progress * endCY;
+ float r = (1 - progress) * startR + progress * endR;
+ float control = (1 - progress) * startControl + progress * endControl;
+ float hShift = (1 - progress) * startHShift + progress * endHShift;
+ float vShift = (1 - progress) * startVShift + progress * endVShift;
+
+ outPath.moveTo(cx, cy - vShift - r);
+ outPath.rLineTo(-hShift, 0);
+
+ addLeftCurve(cx - hShift, cy - vShift, r, control, outPath);
+ outPath.rLineTo(0, vShift + vShift);
+
+ addRightCurve(cx - hShift, cy + vShift, r, control, outPath);
+ outPath.rLineTo(hShift + hShift, 0);
+
+ addLeftCurve(cx + hShift, cy + vShift, -r, -control, outPath);
+ outPath.rLineTo(0, -vShift - vShift);
+
+ addRightCurve(cx + hShift, cy - vShift, -r, -control, outPath);
+ outPath.close();
+ };
+ }
+ }
+
+ /**
+ * Initializes the shape which is closest to closest to the {@link AdaptiveIconDrawable}
+ */
+ public static void init() {
+ if (!Utilities.ATLEAST_OREO) {
+ return;
+ }
+ new MainThreadExecutor().execute(FolderShape::pickShapeInBackground);
+ }
+
+ @TargetApi(Build.VERSION_CODES.O)
+ protected static void pickShapeInBackground() {
+ // Pick any large size
+ int size = 200;
+
+ Region full = new Region(0, 0, size, size);
+ Region iconR = new Region();
+ AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(
+ new ColorDrawable(Color.BLACK), new ColorDrawable(Color.BLACK));
+ drawable.setBounds(0, 0, size, size);
+ iconR.setPath(drawable.getIconMask(), full);
+
+ Path shapePath = new Path();
+ Region shapeR = new Region();
+ Rect tempRect = new Rect();
+
+ // Find the shape with minimum area of divergent region.
+ int minArea = Integer.MAX_VALUE;
+ FolderShape closestShape = null;
+ for (FolderShape shape : getAllShapes()) {
+ shapePath.reset();
+ shape.addShape(shapePath, 0, 0, size / 2f);
+ shapeR.setPath(shapePath, full);
+ shapeR.op(iconR, Op.XOR);
+
+ RegionIterator itr = new RegionIterator(shapeR);
+ int area = 0;
+
+ while (itr.next(tempRect)) {
+ area += tempRect.width() * tempRect.height();
+ }
+ if (area < minArea) {
+ minArea = area;
+ closestShape = shape;
+ }
+ }
+
+ if (closestShape != null) {
+ FolderShape shape = closestShape;
+ new MainThreadExecutor().execute(() -> updateFolderShape(shape));
+ }
+ }
+
+ private static void updateFolderShape(FolderShape shape) {
+ sInstance = shape;
+ LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+ if (app == null) {
+ return;
+ }
+ Launcher launcher = (Launcher) app.getModel().getCallback();
+ if (launcher != null) {
+ launcher.getWorkspace().mapOverItems(MAP_NO_RECURSE, (i, v) -> {
+ if (v instanceof FolderIcon) {
+ v.invalidate();
+ }
+ return false;
+ });
+ }
+ }
+}
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index ceb1a8c..8443953 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -16,6 +16,8 @@
package com.android.launcher3.folder;
+import static com.android.launcher3.folder.FolderShape.getShape;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
@@ -49,16 +51,6 @@
private static final int CONSUMPTION_ANIMATION_DURATION = 100;
- private final PorterDuffXfermode mClipPorterDuffXfermode
- = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
- // Create a RadialGradient such that it draws a black circle and then extends with
- // transparent. To achieve this, we keep the gradient to black for the range [0, 1) and
- // just at the edge quickly change it to transparent.
- private final RadialGradient mClipShader = new RadialGradient(0, 0, 1,
- new int[] {Color.BLACK, Color.BLACK, Color.TRANSPARENT },
- new float[] {0, 0.999f, 1},
- Shader.TileMode.CLAMP);
-
private final PorterDuffXfermode mShadowPorterDuffXfermode
= new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
private RadialGradient mShadowShader = null;
@@ -208,8 +200,7 @@
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(getBgColor());
- drawCircle(canvas, 0 /* deltaRadius */);
-
+ getShape().drawShape(canvas, getOffsetX(), getOffsetY(), getScaledRadius(), mPaint);
drawShadow(canvas);
}
@@ -244,7 +235,7 @@
mPaint.setShader(null);
if (canvas.isHardwareAccelerated()) {
mPaint.setXfermode(mShadowPorterDuffXfermode);
- canvas.drawCircle(radius + offsetX, radius + offsetY, radius, mPaint);
+ getShape().drawShape(canvas, offsetX, offsetY, radius, mPaint);
mPaint.setXfermode(null);
}
@@ -287,7 +278,10 @@
mPaint.setColor(ColorUtils.setAlphaComponent(mBgColor, mStrokeAlpha));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mStrokeWidth);
- drawCircle(canvas, 1 /* deltaRadius */);
+
+ float inset = 1f;
+ getShape().drawShape(canvas,
+ getOffsetX() + inset, getOffsetY() + inset, getScaledRadius() - inset, mPaint);
}
public void drawLeaveBehind(Canvas canvas) {
@@ -296,40 +290,17 @@
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.argb(160, 245, 245, 245));
- drawCircle(canvas, 0 /* deltaRadius */);
+ getShape().drawShape(canvas, getOffsetX(), getOffsetY(), getScaledRadius(), mPaint);
mScale = originalScale;
}
- private void drawCircle(Canvas canvas,float deltaRadius) {
- float radius = getScaledRadius();
- canvas.drawCircle(radius + getOffsetX(), radius + getOffsetY(),
- radius - deltaRadius, mPaint);
- }
-
public Path getClipPath() {
mPath.reset();
- float r = getScaledRadius();
- mPath.addCircle(r + getOffsetX(), r + getOffsetY(), r, Path.Direction.CW);
+ getShape().addShape(mPath, getOffsetX(), getOffsetY(), getScaledRadius());
return mPath;
}
- // It is the callers responsibility to save and restore the canvas layers.
- void clipCanvasHardware(Canvas canvas) {
- mPaint.setColor(Color.BLACK);
- mPaint.setStyle(Paint.Style.FILL);
- mPaint.setXfermode(mClipPorterDuffXfermode);
-
- float radius = getScaledRadius();
- mShaderMatrix.setScale(radius, radius);
- mShaderMatrix.postTranslate(radius + getOffsetX(), radius + getOffsetY());
- mClipShader.setLocalMatrix(mShaderMatrix);
- mPaint.setShader(mClipShader);
- canvas.drawPaint(mPaint);
- mPaint.setXfermode(null);
- mPaint.setShader(null);
- }
-
private void delegateDrawing(CellLayout delegate, int cellX, int cellY) {
if (mDrawingDelegate != delegate) {
delegate.addFolderBackground(this);
diff --git a/src/com/android/launcher3/graphics/IconShapeOverride.java b/src/com/android/launcher3/graphics/IconShapeOverride.java
index cadc6e3..b636c6d 100644
--- a/src/com/android/launcher3/graphics/IconShapeOverride.java
+++ b/src/com/android/launcher3/graphics/IconShapeOverride.java
@@ -26,9 +26,6 @@
import android.content.res.Resources;
import android.os.Build;
import android.os.SystemClock;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
@@ -42,6 +39,9 @@
import java.lang.reflect.Field;
import androidx.annotation.NonNull;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.Preference.OnPreferenceChangeListener;
/**
* Utility class to override shape of {@link android.graphics.drawable.AdaptiveIconDrawable}.
diff --git a/src/com/android/launcher3/icons/BaseIconCache.java b/src/com/android/launcher3/icons/BaseIconCache.java
index 5b74d1d..2afe713 100644
--- a/src/com/android/launcher3/icons/BaseIconCache.java
+++ b/src/com/android/launcher3/icons/BaseIconCache.java
@@ -275,7 +275,7 @@
protected void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
info.title = Utilities.trim(entry.title);
info.contentDescription = entry.contentDescription;
- ((entry.icon == null) ? getDefaultIcon(info.user) : entry).applyTo(info);
+ info.applyFrom((entry.icon == null) ? getDefaultIcon(info.user) : entry);
}
public synchronized BitmapInfo getDefaultIcon(UserHandle user) {
diff --git a/src/com/android/launcher3/icons/BaseIconFactory.java b/src/com/android/launcher3/icons/BaseIconFactory.java
index db723b7..cd60de5 100644
--- a/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -69,14 +69,14 @@
public ShadowGenerator getShadowGenerator() {
if (mShadowGenerator == null) {
- mShadowGenerator = new ShadowGenerator(mContext);
+ mShadowGenerator = new ShadowGenerator(mIconBitmapSize);
}
return mShadowGenerator;
}
public IconNormalizer getNormalizer() {
if (mNormalizer == null) {
- mNormalizer = new IconNormalizer(mContext);
+ mNormalizer = new IconNormalizer(mContext, mIconBitmapSize);
}
return mNormalizer;
}
@@ -192,18 +192,18 @@
}
AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
dr.setBounds(0, 0, 1, 1);
- scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
- if (ATLEAST_OREO && !outShape[0] && !(icon instanceof AdaptiveIconDrawable)) {
+ scale = getNormalizer().getScale(icon, outIconBounds);
+ if (ATLEAST_OREO && !(icon instanceof AdaptiveIconDrawable)) {
FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
fsd.setDrawable(icon);
fsd.setScale(scale);
icon = dr;
- scale = getNormalizer().getScale(icon, outIconBounds, null, null);
+ scale = getNormalizer().getScale(icon, outIconBounds);
((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
}
} else {
- scale = getNormalizer().getScale(icon, outIconBounds, null, null);
+ scale = getNormalizer().getScale(icon, outIconBounds);
}
outScale[0] = scale;
diff --git a/src/com/android/launcher3/icons/BitmapInfo.java b/src/com/android/launcher3/icons/BitmapInfo.java
index ebe0511..245561e 100644
--- a/src/com/android/launcher3/icons/BitmapInfo.java
+++ b/src/com/android/launcher3/icons/BitmapInfo.java
@@ -18,8 +18,6 @@
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
-import com.android.launcher3.ItemInfoWithIcon;
-
public class BitmapInfo {
public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8);
@@ -27,11 +25,6 @@
public Bitmap icon;
public int color;
- public void applyTo(ItemInfoWithIcon info) {
- info.iconBitmap = icon;
- info.iconColor = color;
- }
-
public void applyTo(BitmapInfo info) {
info.icon = icon;
info.color = color;
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 4349455..41a53e5 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -139,7 +139,7 @@
// null info means not installed, but if we have a component from the intent then
// we should still look in the cache for restored app icons.
if (info.getTargetComponent() == null) {
- getDefaultIcon(info.user).applyTo(info);
+ info.applyFrom(getDefaultIcon(info.user));
info.title = "";
info.contentDescription = "";
} else {
diff --git a/src/com/android/launcher3/icons/IconNormalizer.java b/src/com/android/launcher3/icons/IconNormalizer.java
index 7317782..8eb8252 100644
--- a/src/com/android/launcher3/icons/IconNormalizer.java
+++ b/src/com/android/launcher3/icons/IconNormalizer.java
@@ -20,21 +20,13 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.Matrix;
import android.graphics.Paint;
-import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.util.Log;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import java.nio.ByteBuffer;
@@ -58,9 +50,6 @@
private static final int MIN_VISIBLE_ALPHA = 40;
- // Shape detection related constants
- private static final float BOUND_RATIO_MARGIN = .05f;
- private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f;
private static final float SCALE_NOT_INITIALIZED = 0;
// Ratio of the diameter of an normalized circular icon to the actual icon size.
@@ -69,8 +58,6 @@
private final int mMaxSize;
private final Bitmap mBitmap;
private final Canvas mCanvas;
- private final Paint mPaintMaskShape;
- private final Paint mPaintMaskShapeOutline;
private final byte[] mPixels;
private final Rect mAdaptiveIconBounds;
@@ -80,13 +67,11 @@
private final float[] mLeftBorder;
private final float[] mRightBorder;
private final Rect mBounds;
- private final Path mShapePath;
- private final Matrix mMatrix;
/** package private **/
- IconNormalizer(Context context) {
+ IconNormalizer(Context context, int iconBitmapSize) {
// Use twice the icon size as maximum size to avoid scaling down twice.
- mMaxSize = LauncherAppState.getIDP(context).iconBitmapSize * 2;
+ mMaxSize = iconBitmapSize * 2;
mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
mCanvas = new Canvas(mBitmap);
mPixels = new byte[mMaxSize * mMaxSize];
@@ -95,89 +80,10 @@
mBounds = new Rect();
mAdaptiveIconBounds = new Rect();
- mPaintMaskShape = new Paint();
- mPaintMaskShape.setColor(Color.RED);
- mPaintMaskShape.setStyle(Paint.Style.FILL);
- mPaintMaskShape.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
-
- mPaintMaskShapeOutline = new Paint();
- mPaintMaskShapeOutline.setStrokeWidth(2 * context.getResources().getDisplayMetrics().density);
- mPaintMaskShapeOutline.setStyle(Paint.Style.STROKE);
- mPaintMaskShapeOutline.setColor(Color.BLACK);
- mPaintMaskShapeOutline.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
-
- mShapePath = new Path();
- mMatrix = new Matrix();
mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
}
/**
- * Returns if the shape of the icon is same as the path.
- * For this method to work, the shape path bounds should be in [0,1]x[0,1] bounds.
- */
- private boolean isShape(Path maskPath) {
- // Condition1:
- // If width and height of the path not close to a square, then the icon shape is
- // not same as the mask shape.
- float iconRatio = ((float) mBounds.width()) / mBounds.height();
- if (Math.abs(iconRatio - 1) > BOUND_RATIO_MARGIN) {
- if (DEBUG) {
- Log.d(TAG, "Not same as mask shape because width != height. " + iconRatio);
- }
- return false;
- }
-
- // Condition 2:
- // Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation
- // should generate transparent image, if the actual icon is equivalent to the shape.
-
- // Fit the shape within the icon's bounding box
- mMatrix.reset();
- mMatrix.setScale(mBounds.width(), mBounds.height());
- mMatrix.postTranslate(mBounds.left, mBounds.top);
- maskPath.transform(mMatrix, mShapePath);
-
- // XOR operation
- mCanvas.drawPath(mShapePath, mPaintMaskShape);
-
- // DST_OUT operation around the mask path outline
- mCanvas.drawPath(mShapePath, mPaintMaskShapeOutline);
-
- // Check if the result is almost transparent
- return isTransparentBitmap();
- }
-
- /**
- * Used to determine if certain the bitmap is transparent.
- */
- private boolean isTransparentBitmap() {
- ByteBuffer buffer = ByteBuffer.wrap(mPixels);
- buffer.rewind();
- mBitmap.copyPixelsToBuffer(buffer);
-
- int y = mBounds.top;
- // buffer position
- int index = y * mMaxSize;
- // buffer shift after every row, width of buffer = mMaxSize
- int rowSizeDiff = mMaxSize - mBounds.right;
-
- int sum = 0;
- for (; y < mBounds.bottom; y++) {
- index += mBounds.left;
- for (int x = mBounds.left; x < mBounds.right; x++) {
- if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
- sum++;
- }
- index++;
- }
- index += rowSizeDiff;
- }
-
- float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height());
- return percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
- }
-
- /**
* Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
* matches the design guidelines for a launcher icon.
*
@@ -191,19 +97,14 @@
*
* @param outBounds optional rect to receive the fraction distance from each edge.
*/
- public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds,
- @Nullable Path path, @Nullable boolean[] outMaskShape) {
- if (Utilities.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {
+ public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds) {
+ if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {
if (mAdaptiveIconScale != SCALE_NOT_INITIALIZED) {
if (outBounds != null) {
outBounds.set(mAdaptiveIconBounds);
}
return mAdaptiveIconScale;
}
- if (d instanceof FolderAdaptiveIcon) {
- // Since we just want the scale, avoid heavy drawing operations
- d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null);
- }
}
int width = d.getIntrinsicWidth();
int height = d.getIntrinsicHeight();
@@ -307,14 +208,10 @@
1 - ((float) mBounds.right) / width,
1 - ((float) mBounds.bottom) / height);
}
-
- if (outMaskShape != null && outMaskShape.length > 0) {
- outMaskShape[0] = isShape(path);
- }
float areaScale = area / (width * height);
// Use sqrt of the final ratio as the images is scaled across both width and height.
float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
- if (Utilities.ATLEAST_OREO && d instanceof AdaptiveIconDrawable &&
+ if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable &&
mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
mAdaptiveIconScale = scale;
mAdaptiveIconBounds.set(mBounds);
diff --git a/src/com/android/launcher3/icons/ShadowGenerator.java b/src/com/android/launcher3/icons/ShadowGenerator.java
index 57d463a..6491b7e 100644
--- a/src/com/android/launcher3/icons/ShadowGenerator.java
+++ b/src/com/android/launcher3/icons/ShadowGenerator.java
@@ -16,7 +16,6 @@
package com.android.launcher3.icons;
-import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BlurMaskFilter;
@@ -28,23 +27,19 @@
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
-import com.android.launcher3.LauncherAppState;
-
import androidx.core.graphics.ColorUtils;
/**
* Utility class to add shadows to bitmaps.
*/
public class ShadowGenerator {
-
- // Percent of actual icon size
- private static final float HALF_DISTANCE = 0.5f;
public static final float BLUR_FACTOR = 0.5f/48;
// Percent of actual icon size
public static final float KEY_SHADOW_DISTANCE = 1f/48;
private static final int KEY_SHADOW_ALPHA = 61;
-
+ // Percent of actual icon size
+ private static final float HALF_DISTANCE = 0.5f;
private static final int AMBIENT_SHADOW_ALPHA = 30;
private final int mIconSize;
@@ -53,8 +48,8 @@
private final Paint mDrawPaint;
private final BlurMaskFilter mDefaultBlurMaskFilter;
- public ShadowGenerator(Context context) {
- mIconSize = LauncherAppState.getIDP(context).iconBitmapSize;
+ public ShadowGenerator(int iconSize) {
+ mIconSize = iconSize;
mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL);
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index fcdc088..c9d8e3e 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -27,10 +27,10 @@
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.widget.WidgetListRowEntry;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.concurrent.Executor;
/**
@@ -107,13 +107,9 @@
}
public void bindDeepShortcuts(BgDataModel dataModel) {
- final MultiHashMap<ComponentKey, String> shortcutMapCopy = dataModel.deepShortcutMap.clone();
- scheduleCallbackTask(new CallbackTask() {
- @Override
- public void execute(Callbacks callbacks) {
- callbacks.bindDeepShortcutMap(shortcutMapCopy);
- }
- });
+ final HashMap<ComponentKey, Integer> shortcutMapCopy =
+ new HashMap<>(dataModel.deepShortcutMap);
+ scheduleCallbackTask(callbacks -> callbacks.bindDeepShortcutMap(shortcutMapCopy));
}
public void bindUpdatedWidgets(BgDataModel dataModel) {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 81eefc4..151d6f4 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -38,7 +38,6 @@
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
-import com.android.launcher3.util.MultiHashMap;
import com.google.protobuf.nano.MessageNano;
import java.io.FileDescriptor;
@@ -97,9 +96,9 @@
public boolean hasShortcutHostPermission;
/**
- * Maps all launcher activities to the id's of their shortcuts (if they have any).
+ * Maps all launcher activities to counts of their shortcuts.
*/
- public final MultiHashMap<ComponentKey, String> deepShortcutMap = new MultiHashMap<>();
+ public final HashMap<ComponentKey, Integer> deepShortcutMap = new HashMap<>();
/**
* Entire list of widgets.
@@ -154,14 +153,11 @@
}
if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
- writer.println(prefix + "shortcuts");
- for (ArrayList<String> map : deepShortcutMap.values()) {
- writer.print(prefix + " ");
- for (String str : map) {
- writer.print(str + ", ");
- }
- writer.println();
+ writer.println(prefix + "shortcut counts ");
+ for (Integer count : deepShortcutMap.values()) {
+ writer.print(count + ", ");
}
+ writer.println();
}
}
@@ -359,9 +355,9 @@
}
/**
- * Clear all the deep shortcuts for the given package, and re-add the new shortcuts.
+ * Clear all the deep shortcut counts for the given package, and re-add the new shortcut counts.
*/
- public synchronized void updateDeepShortcutMap(
+ public synchronized void updateDeepShortcutCounts(
String packageName, UserHandle user, List<ShortcutInfoCompat> shortcuts) {
if (packageName != null) {
Iterator<ComponentKey> keysIter = deepShortcutMap.keySet().iterator();
@@ -381,7 +377,9 @@
if (shouldShowInContainer) {
ComponentKey targetComponent
= new ComponentKey(shortcut.getActivity(), shortcut.getUserHandle());
- deepShortcutMap.addToList(targetComponent, shortcut.getId());
+
+ Integer previousCount = deepShortcutMap.get(targetComponent);
+ deepShortcutMap.put(targetComponent, previousCount == null ? 1 : previousCount + 1);
}
}
}
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 94cf5c2..ea4d32b 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -153,7 +153,7 @@
info.title = getTitle();
// the fallback icon
if (!loadIcon(info)) {
- mIconCache.getDefaultIcon(info.user).applyTo(info);
+ info.applyFrom(mIconCache.getDefaultIcon(info.user));
}
// TODO: If there's an explicit component and we can't install that, delete it.
@@ -176,7 +176,7 @@
BitmapInfo iconInfo = li.createIconBitmap(info.iconResource);
li.recycle();
if (iconInfo != null) {
- iconInfo.applyTo(info);
+ info.applyFrom(iconInfo);
return true;
}
}
@@ -185,7 +185,7 @@
// Failed to load from resource, try loading from DB.
byte[] data = getBlob(iconIndex);
try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
- li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length)).applyTo(info);
+ info.applyFrom(li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length)));
return true;
} catch (Exception e) {
Log.e(TAG, "Failed to load icon for info " + info, e);
diff --git a/src/com/android/launcher3/model/LoaderResults.java b/src/com/android/launcher3/model/LoaderResults.java
index 2c15df1..1d18e76 100644
--- a/src/com/android/launcher3/model/LoaderResults.java
+++ b/src/com/android/launcher3/model/LoaderResults.java
@@ -34,7 +34,6 @@
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.LooperIdleLock;
-import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.launcher3.widget.WidgetListRowEntry;
@@ -42,9 +41,8 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
-import java.util.HashSet;
+import java.util.HashMap;
import java.util.Iterator;
-import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -333,20 +331,16 @@
}
public void bindDeepShortcuts() {
- final MultiHashMap<ComponentKey, String> shortcutMapCopy;
+ final HashMap<ComponentKey, Integer> shortcutMapCopy;
synchronized (mBgDataModel) {
- shortcutMapCopy = mBgDataModel.deepShortcutMap.clone();
+ shortcutMapCopy = new HashMap<>(mBgDataModel.deepShortcutMap);
}
- Runnable r = new Runnable() {
- @Override
- public void run() {
- Callbacks callbacks = mCallbacks.get();
- if (callbacks != null) {
- callbacks.bindDeepShortcutMap(shortcutMapCopy);
- }
+ mUiExecutor.execute(() -> {
+ Callbacks callbacks = mCallbacks.get();
+ if (callbacks != null) {
+ callbacks.bindDeepShortcutMap(shortcutMapCopy);
}
- };
- mUiExecutor.execute(r);
+ });
}
public void bindAllApps() {
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 1ec7af2..8b3e2c9 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -498,8 +498,8 @@
c.loadIcon(finalInfo) ? finalInfo.iconBitmap : null;
LauncherIcons li = LauncherIcons.obtain(context);
- li.createShortcutIcon(pinnedShortcut,
- true /* badged */, fallbackIconProvider).applyTo(info);
+ info.applyFrom(li.createShortcutIcon(pinnedShortcut,
+ true /* badged */, fallbackIconProvider));
li.recycle();
if (pmHelper.isAppSuspended(
pinnedShortcut.getPackage(), info.user)) {
@@ -861,7 +861,7 @@
if (mUserManager.isUserUnlocked(user)) {
List<ShortcutInfoCompat> shortcuts =
mShortcutManager.queryForAllShortcuts(user);
- mBgDataModel.updateDeepShortcutMap(null, user, shortcuts);
+ mBgDataModel.updateDeepShortcutCounts(null, user, shortcuts);
}
}
}
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 201a63e..0f67f0c 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -194,7 +194,7 @@
BitmapInfo iconInfo = li.createIconBitmap(si.iconResource);
li.recycle();
if (iconInfo != null) {
- iconInfo.applyTo(si);
+ si.applyFrom(iconInfo);
infoUpdated = true;
}
}
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 020bc41..e99fed9 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -96,8 +96,8 @@
// If the shortcut is pinned but no longer has an icon in the system,
// keep the current icon instead of reverting to the default icon.
LauncherIcons li = LauncherIcons.obtain(context);
- li.createShortcutIcon(fullDetails, true, Provider.of(shortcutInfo.iconBitmap))
- .applyTo(shortcutInfo);
+ shortcutInfo.applyFrom(li.createShortcutIcon(fullDetails, true,
+ Provider.of(shortcutInfo.iconBitmap)));
li.recycle();
updatedShortcutInfos.add(shortcutInfo);
}
@@ -116,7 +116,7 @@
if (mUpdateIdMap) {
// Update the deep shortcut map if the list of ids has changed for an activity.
- dataModel.updateDeepShortcutMap(mPackageName, mUser, mShortcuts);
+ dataModel.updateDeepShortcutCounts(mPackageName, mUser, mShortcuts);
bindDeepShortcuts(dataModel);
}
}
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 9f02d4f..8e7557a 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -95,7 +95,7 @@
// If the shortcut is pinned but no longer has an icon in the system,
// keep the current icon instead of reverting to the default icon.
LauncherIcons li = LauncherIcons.obtain(context);
- li.createShortcutIcon(shortcut, true, Provider.of(si.iconBitmap)).applyTo(si);
+ si.applyFrom(li.createShortcutIcon(shortcut, true, Provider.of(si.iconBitmap)));
li.recycle();
} else {
si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
@@ -117,7 +117,7 @@
}
if (isUserUnlocked) {
- dataModel.updateDeepShortcutMap(
+ dataModel.updateDeepShortcutCounts(
null, mUser, deepShortcutManager.queryForAllShortcuts(mUser));
}
bindDeepShortcuts(dataModel);
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index b9e6a98..4f1fcda 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -216,13 +216,13 @@
BubbleTextView icon, ItemInfo item, SystemShortcutFactory factory) {
PopupDataProvider popupDataProvider = mLauncher.getPopupDataProvider();
populateAndShow(icon,
- popupDataProvider.getShortcutIdsForItem(item),
+ popupDataProvider.getShortcutCountForItem(item),
popupDataProvider.getNotificationKeysForItem(item),
factory.getEnabledShortcuts(mLauncher, item));
}
@TargetApi(Build.VERSION_CODES.P)
- protected void populateAndShow(final BubbleTextView originalIcon, final List<String> shortcutIds,
+ protected void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
mNumNotifications = notificationKeys.size();
mOriginalIcon = originalIcon;
@@ -240,12 +240,12 @@
int viewsToFlip = getChildCount();
mSystemShortcutContainer = this;
- if (!shortcutIds.isEmpty()) {
+ if (shortcutCount > 0) {
if (mNotificationItemView != null) {
mNotificationItemView.addGutter();
}
- for (int i = shortcutIds.size(); i > 0; i--) {
+ for (int i = shortcutCount; i > 0; i--) {
mShortcuts.add(inflateAndAdd(R.layout.deep_shortcut, this));
}
updateHiddenShortcuts();
@@ -284,7 +284,7 @@
final Looper workerLooper = LauncherModel.getWorkerLooper();
new Handler(workerLooper).postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()),
- this, shortcutIds, mShortcuts, notificationKeys));
+ this, mShortcuts, notificationKeys));
}
private String getTitleForAccessibility() {
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 4d5a9c6..3206503 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -29,7 +29,6 @@
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.WidgetListRowEntry;
@@ -52,8 +51,8 @@
private final Launcher mLauncher;
- /** Maps launcher activity components to their list of shortcut ids. */
- private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>();
+ /** Maps launcher activity components to a count of how many shortcuts they have. */
+ private HashMap<ComponentKey, Integer> mDeepShortcutMap = new HashMap<>();
/** Maps packages to their BadgeInfo's . */
private Map<PackageUserKey, BadgeInfo> mPackageUserToBadgeInfos = new HashMap<>();
/** Maps packages to their Widgets */
@@ -146,22 +145,22 @@
}
}
- public void setDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) {
+ public void setDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) {
mDeepShortcutMap = deepShortcutMapCopy;
if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap);
}
- public List<String> getShortcutIdsForItem(ItemInfo info) {
+ public int getShortcutCountForItem(ItemInfo info) {
if (!DeepShortcutManager.supportsShortcuts(info)) {
- return Collections.EMPTY_LIST;
+ return 0;
}
ComponentName component = info.getTargetComponent();
if (component == null) {
- return Collections.EMPTY_LIST;
+ return 0;
}
- List<String> ids = mDeepShortcutMap.get(new ComponentKey(component, info.user));
- return ids == null ? Collections.EMPTY_LIST : ids;
+ Integer count = mDeepShortcutMap.get(new ComponentKey(component, info.user));
+ return count == null ? 0 : count;
}
public BadgeInfo getBadgeInfoForItem(ItemInfo info) {
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index c14c00e..2c59202 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -124,7 +124,7 @@
public static Runnable createUpdateRunnable(final Launcher launcher, final ItemInfo originalInfo,
final Handler uiHandler, final PopupContainerWithArrow container,
- final List<String> shortcutIds, final List<DeepShortcutView> shortcutViews,
+ final List<DeepShortcutView> shortcutViews,
final List<NotificationKeyData> notificationKeys) {
final ComponentName activity = originalInfo.getTargetComponent();
final UserHandle user = originalInfo.user;
@@ -141,7 +141,7 @@
}
List<ShortcutInfoCompat> shortcuts = DeepShortcutManager.getInstance(launcher)
- .queryForShortcutsContainer(activity, shortcutIds, user);
+ .queryForShortcutsContainer(activity, user);
String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null
: notificationKeys.get(0).shortcutId;
shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
@@ -150,7 +150,7 @@
final ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
// Use unbadged icon for the menu.
LauncherIcons li = LauncherIcons.obtain(launcher);
- li.createShortcutIcon(shortcut, false /* badged */).applyTo(si);
+ si.applyFrom(li.createShortcutIcon(shortcut, false /* badged */));
li.recycle();
si.rank = i;
diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java
new file mode 100644
index 0000000..c76fb96
--- /dev/null
+++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.popup;
+
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
+public class RemoteActionShortcut extends SystemShortcut<Launcher> {
+ private static final String TAG = "RemoteActionShortcut";
+
+ private final RemoteAction mAction;
+
+ public RemoteActionShortcut(RemoteAction action) {
+ super(action.getIcon(), action.getTitle(), action.getContentDescription(),
+ R.id.action_remote_action_shortcut);
+ mAction = action;
+ }
+
+ @Override
+ public View.OnClickListener getOnClickListener(
+ final Launcher launcher, final ItemInfo itemInfo) {
+ return view -> {
+ AbstractFloatingView.closeAllOpenViews(launcher);
+
+ try {
+ mAction.getActionIntent().send(
+ launcher,
+ 0,
+ new Intent().putExtra(
+ Intent.EXTRA_PACKAGE_NAME,
+ itemInfo.getTargetComponent().getPackageName()),
+ (pendingIntent, intent, resultCode, resultData, resultExtras) -> {
+ if (resultData != null && !resultData.isEmpty()) {
+ Log.e(TAG, "Remote action returned result: " + mAction.getTitle()
+ + " : " + resultData);
+ Toast.makeText(launcher, resultData, Toast.LENGTH_SHORT).show();
+ }
+ },
+ new Handler(Looper.getMainLooper()));
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Remote action canceled: " + mAction.getTitle(), e);
+ Toast.makeText(launcher, launcher.getString(
+ R.string.remote_action_failed,
+ mAction.getTitle()),
+ Toast.LENGTH_SHORT)
+ .show();
+ }
+
+ launcher.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP,
+ LauncherLogProto.ControlType.REMOTE_ACTION_SHORTCUT, view);
+ };
+ }
+}
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index b80ba8a..f9a2007 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -6,8 +6,10 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
@@ -36,7 +38,7 @@
public abstract class SystemShortcut<T extends BaseDraggingActivity> extends ItemInfo {
private final int mIconResId;
private final int mLabelResId;
- private final Drawable mIcon;
+ private final Icon mIcon;
private final CharSequence mLabel;
private final CharSequence mContentDescription;
private final int mAccessibilityActionId;
@@ -50,7 +52,7 @@
mContentDescription = null;
}
- public SystemShortcut(Drawable icon, CharSequence label, CharSequence contentDescription,
+ public SystemShortcut(Icon icon, CharSequence label, CharSequence contentDescription,
int accessibilityActionId) {
mIcon = icon;
mLabel = label;
@@ -71,7 +73,9 @@
public void setIconAndLabelFor(View iconView, TextView labelView) {
if (mIcon != null) {
- iconView.setBackground(mIcon);
+ mIcon.loadDrawableAsync(iconView.getContext(),
+ iconView::setBackground,
+ new Handler(Looper.getMainLooper()));
} else {
iconView.setBackgroundResource(mIconResId);
}
@@ -85,7 +89,9 @@
public void setIconAndContentDescriptionFor(ImageView view) {
if (mIcon != null) {
- view.setImageDrawable(mIcon);
+ mIcon.loadDrawableAsync(view.getContext(),
+ view::setImageDrawable,
+ new Handler(Looper.getMainLooper()));
} else {
view.setImageResource(mIconResId);
}
diff --git a/src/com/android/launcher3/settings/IconBadgingPreference.java b/src/com/android/launcher3/settings/IconBadgingPreference.java
new file mode 100644
index 0000000..7c97b38
--- /dev/null
+++ b/src/com/android/launcher3/settings/IconBadgingPreference.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.settings;
+
+import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
+import static com.android.launcher3.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGS;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.launcher3.notification.NotificationListener;
+import com.android.launcher3.util.SecureSettingsObserver;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+/**
+ * A {@link Preference} for indicating icon badging status.
+ * Also has utility methods for updating UI based on badging status changes.
+ */
+public class IconBadgingPreference extends Preference
+ implements SecureSettingsObserver.OnChangeListener {
+
+ private boolean mWidgetFrameVisible = false;
+
+ /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
+ private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
+
+ public IconBadgingPreference(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public IconBadgingPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public IconBadgingPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public IconBadgingPreference(Context context) {
+ super(context);
+ }
+
+ private void setWidgetFrameVisible(boolean isVisible) {
+ if (mWidgetFrameVisible != isVisible) {
+ mWidgetFrameVisible = isVisible;
+ notifyChanged();
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ View widgetFrame = holder.findViewById(android.R.id.widget_frame);
+ if (widgetFrame != null) {
+ widgetFrame.setVisibility(mWidgetFrameVisible ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ @Override
+ public void onSettingsChanged(boolean enabled) {
+ int summary = enabled ? R.string.icon_badging_desc_on : R.string.icon_badging_desc_off;
+
+ boolean serviceEnabled = true;
+ if (enabled) {
+ // Check if the listener is enabled or not.
+ String enabledListeners = Settings.Secure.getString(
+ getContext().getContentResolver(), NOTIFICATION_ENABLED_LISTENERS);
+ ComponentName myListener =
+ new ComponentName(getContext(), NotificationListener.class);
+ serviceEnabled = enabledListeners != null &&
+ (enabledListeners.contains(myListener.flattenToString()) ||
+ enabledListeners.contains(myListener.flattenToShortString()));
+ if (!serviceEnabled) {
+ summary = R.string.title_missing_notification_access;
+ }
+ }
+ setWidgetFrameVisible(!serviceEnabled);
+ setFragment(serviceEnabled ? null : NotificationAccessConfirmation.class.getName());
+ setSummary(summary);
+ }
+
+ public static class NotificationAccessConfirmation
+ extends DialogFragment implements DialogInterface.OnClickListener {
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+ String msg = context.getString(R.string.msg_missing_notification_access,
+ context.getString(R.string.derived_app_name));
+ return new AlertDialog.Builder(context)
+ .setTitle(R.string.title_missing_notification_access)
+ .setMessage(msg)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(R.string.title_change_settings, this)
+ .create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ ComponentName cn = new ComponentName(getActivity(), NotificationListener.class);
+ Bundle showFragmentArgs = new Bundle();
+ showFragmentArgs.putString(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString());
+
+ Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString())
+ .putExtra(EXTRA_SHOW_FRAGMENT_ARGS, showFragmentArgs);
+ getActivity().startActivity(intent);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/settings/PreferenceHighlighter.java b/src/com/android/launcher3/settings/PreferenceHighlighter.java
new file mode 100644
index 0000000..4ed4cf1
--- /dev/null
+++ b/src/com/android/launcher3/settings/PreferenceHighlighter.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.settings;
+
+import static androidx.core.graphics.ColorUtils.setAlphaComponent;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.Property;
+import android.view.View;
+
+import com.android.launcher3.util.Themes;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
+import androidx.recyclerview.widget.RecyclerView.State;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+/**
+ * Utility class for highlighting a preference
+ */
+public class PreferenceHighlighter extends ItemDecoration implements Runnable {
+
+ private static final Property<PreferenceHighlighter, Integer> HIGHLIGHT_COLOR =
+ new Property<PreferenceHighlighter, Integer>(Integer.TYPE, "highlightColor") {
+
+ @Override
+ public Integer get(PreferenceHighlighter highlighter) {
+ return highlighter.mHighlightColor;
+ }
+
+ @Override
+ public void set(PreferenceHighlighter highlighter, Integer value) {
+ highlighter.mHighlightColor = value;
+ highlighter.mRv.invalidateItemDecorations();
+ }
+ };
+
+ private static final long HIGHLIGHT_DURATION = 15000L;
+ private static final long HIGHLIGHT_FADE_OUT_DURATION = 500L;
+ private static final long HIGHLIGHT_FADE_IN_DURATION = 200L;
+ private static final int END_COLOR = setAlphaComponent(Color.WHITE, 0);
+
+ private final Paint mPaint = new Paint();
+ private final RecyclerView mRv;
+ private final int mIndex;
+
+ private boolean mHighLightStarted = false;
+ private int mHighlightColor = END_COLOR;
+
+
+ public PreferenceHighlighter(RecyclerView rv, int index) {
+ mRv = rv;
+ mIndex = index;
+ }
+
+ @Override
+ public void run() {
+ mRv.addItemDecoration(this);
+ mRv.smoothScrollToPosition(mIndex);
+ }
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView parent, State state) {
+ ViewHolder holder = parent.findViewHolderForAdapterPosition(mIndex);
+ if (holder == null) {
+ return;
+ }
+ if (!mHighLightStarted && state.getRemainingScrollVertical() != 0) {
+ // Wait until scrolling stopped
+ return;
+ }
+
+ if (!mHighLightStarted) {
+ // Start highlight
+ int colorTo = setAlphaComponent(Themes.getColorAccent(mRv.getContext()), 66);
+ ObjectAnimator anim = ObjectAnimator.ofArgb(this, HIGHLIGHT_COLOR, END_COLOR, colorTo);
+ anim.setDuration(HIGHLIGHT_FADE_IN_DURATION);
+ anim.setRepeatMode(ValueAnimator.REVERSE);
+ anim.setRepeatCount(4);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ removeHighlight();
+ }
+ });
+ anim.start();
+ mHighLightStarted = true;
+ }
+
+ View view = holder.itemView;
+ mPaint.setColor(mHighlightColor);
+ c.drawRect(0, view.getY(), parent.getWidth(), view.getY() + view.getHeight(), mPaint);
+ }
+
+ private void removeHighlight() {
+ ObjectAnimator anim = ObjectAnimator.ofArgb(
+ this, HIGHLIGHT_COLOR, mHighlightColor, END_COLOR);
+ anim.setDuration(HIGHLIGHT_FADE_OUT_DURATION);
+ anim.setStartDelay(HIGHLIGHT_DURATION);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mRv.removeItemDecoration(PreferenceHighlighter.this);
+ }
+ });
+ anim.start();
+ }
+}
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
new file mode 100644
index 0000000..4c022b4
--- /dev/null
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.settings;
+
+import static com.android.launcher3.SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY;
+import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
+import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
+import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
+
+import android.app.Activity;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.IconShapeOverride;
+import com.android.launcher3.util.SecureSettingsObserver;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.PreferenceFragment.OnPreferenceStartFragmentCallback;
+import androidx.preference.PreferenceFragment.OnPreferenceStartScreenCallback;
+import androidx.preference.PreferenceGroup.PreferencePositionCallback;
+import androidx.preference.PreferenceScreen;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * Settings activity for Launcher. Currently implements the following setting: Allow rotation
+ */
+public class SettingsActivity extends Activity
+ implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback {
+
+ private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
+
+ private static final String ICON_BADGING_PREFERENCE_KEY = "pref_icon_badging";
+ /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
+ private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
+
+ public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+ public static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
+ private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
+ public static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState == null) {
+ Bundle args = new Bundle();
+ String prefKey = getIntent().getStringExtra(EXTRA_FRAGMENT_ARG_KEY);
+ if (!TextUtils.isEmpty(prefKey)) {
+ args.putString(EXTRA_FRAGMENT_ARG_KEY, prefKey);
+ }
+
+ Fragment f = Fragment.instantiate(
+ this, getString(R.string.settings_fragment_name), args);
+ // Display the fragment as the main content.
+ getFragmentManager().beginTransaction()
+ .replace(android.R.id.content, f)
+ .commit();
+ }
+ }
+
+ private boolean startFragment(String fragment, Bundle args, String key) {
+ if (Utilities.ATLEAST_P && getFragmentManager().isStateSaved()) {
+ // Sometimes onClick can come after onPause because of being posted on the handler.
+ // Skip starting new fragments in that case.
+ return false;
+ }
+ Fragment f = Fragment.instantiate(this, fragment, args);
+ if (f instanceof DialogFragment) {
+ ((DialogFragment) f).show(getFragmentManager(), key);
+ } else {
+ getFragmentManager()
+ .beginTransaction()
+ .replace(android.R.id.content, f)
+ .addToBackStack(key)
+ .commit();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onPreferenceStartFragment(
+ PreferenceFragment preferenceFragment, Preference pref) {
+ return startFragment(pref.getFragment(), pref.getExtras(), pref.getKey());
+ }
+
+ @Override
+ public boolean onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref) {
+ Bundle args = new Bundle();
+ args.putString(PreferenceFragment.ARG_PREFERENCE_ROOT, pref.getKey());
+ return startFragment(getString(R.string.settings_fragment_name), args, pref.getKey());
+ }
+
+ /**
+ * This fragment shows the launcher preferences.
+ */
+ public static class LauncherSettingsFragment extends PreferenceFragment {
+
+ private SecureSettingsObserver mIconBadgingObserver;
+
+ private String mHighLightKey;
+ private boolean mPreferenceHighlighted = false;
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ final Bundle args = getArguments();
+ mHighLightKey = args == null ? null : args.getString(EXTRA_FRAGMENT_ARG_KEY);
+ if (rootKey == null && !TextUtils.isEmpty(mHighLightKey)) {
+ rootKey = getParentKeyForPref(mHighLightKey);
+ }
+
+ if (savedInstanceState != null) {
+ mPreferenceHighlighted = savedInstanceState.getBoolean(SAVE_HIGHLIGHTED_KEY);
+ }
+
+ getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY);
+ setPreferencesFromResource(R.xml.launcher_preferences, rootKey);
+
+ PreferenceScreen screen = getPreferenceScreen();
+ for (int i = screen.getPreferenceCount() - 1; i >= 0; i--) {
+ Preference preference = screen.getPreference(i);
+ if (!initPreference(preference)) {
+ screen.removePreference(preference);
+ }
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
+ }
+
+ protected String getParentKeyForPref(String key) {
+ return null;
+ }
+
+ /**
+ * Initializes a preference. This is called for every preference. Returning false here
+ * will remove that preference from the list.
+ */
+ protected boolean initPreference(Preference preference) {
+ switch (preference.getKey()) {
+ case ICON_BADGING_PREFERENCE_KEY:
+ if (!Utilities.ATLEAST_OREO ||
+ !getResources().getBoolean(R.bool.notification_badging_enabled)) {
+ return false;
+ }
+
+ // Listen to system notification badge settings while this UI is active.
+ mIconBadgingObserver = newNotificationSettingsObserver(
+ getActivity(), (IconBadgingPreference) preference);
+ mIconBadgingObserver.register();
+ // Also listen if notification permission changes
+ mIconBadgingObserver.getResolver().registerContentObserver(
+ Settings.Secure.getUriFor(NOTIFICATION_ENABLED_LISTENERS), false,
+ mIconBadgingObserver);
+ mIconBadgingObserver.dispatchOnChange();
+ return true;
+
+ case ADD_ICON_PREFERENCE_KEY:
+ return Utilities.ATLEAST_OREO;
+
+ case IconShapeOverride.KEY_PREFERENCE:
+ if (!IconShapeOverride.isSupported(getActivity())) {
+ return false;
+ }
+ IconShapeOverride.handlePreferenceUi((ListPreference) preference);
+ return true;
+
+ case ALLOW_ROTATION_PREFERENCE_KEY:
+ if (getResources().getBoolean(R.bool.allow_rotation)) {
+ // Launcher supports rotation by default. No need to show this setting.
+ return false;
+ }
+ // Initialize the UI once
+ preference.setDefaultValue(getAllowRotationDefaultValue());
+ return true;
+
+ case FLAGS_PREFERENCE_KEY:
+ // Only show flag toggler UI if this build variant implements that.
+ return FeatureFlags.showFlagTogglerUi(getContext());
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ if (isAdded() && !mPreferenceHighlighted) {
+ PreferenceHighlighter highlighter = createHighlighter();
+ if (highlighter != null) {
+ getView().postDelayed(highlighter, DELAY_HIGHLIGHT_DURATION_MILLIS);
+ mPreferenceHighlighted = true;
+ }
+ }
+ }
+
+ private PreferenceHighlighter createHighlighter() {
+ if (TextUtils.isEmpty(mHighLightKey)) {
+ return null;
+ }
+
+ PreferenceScreen screen = getPreferenceScreen();
+ if (screen == null) {
+ return null;
+ }
+
+ RecyclerView list = getListView();
+ PreferencePositionCallback callback = (PreferencePositionCallback) list.getAdapter();
+ int position = callback.getPreferenceAdapterPosition(mHighLightKey);
+ return position >= 0 ? new PreferenceHighlighter(list, position) : null;
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mIconBadgingObserver != null) {
+ mIconBadgingObserver.unregister();
+ mIconBadgingObserver = null;
+ }
+ super.onDestroy();
+ }
+ }
+}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
index 24e2e2f..e70aac6 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -94,10 +94,11 @@
* Gets all the manifest and dynamic shortcuts associated with the given package and user,
* to be displayed in the shortcuts container on long press.
*/
+ @TargetApi(25)
public List<ShortcutInfoCompat> queryForShortcutsContainer(ComponentName activity,
- List<String> ids, UserHandle user) {
+ UserHandle user) {
return query(ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC,
- activity.getPackageName(), activity, ids, user);
+ activity.getPackageName(), activity, null, user);
}
/**
diff --git a/src/com/android/launcher3/util/ListViewHighlighter.java b/src/com/android/launcher3/util/ListViewHighlighter.java
deleted file mode 100644
index c9fe228..0000000
--- a/src/com/android/launcher3/util/ListViewHighlighter.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.util;
-
-import android.animation.ArgbEvaluator;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-import android.view.View.OnLayoutChangeListener;
-import android.widget.AbsListView;
-import android.widget.AbsListView.OnScrollListener;
-import android.widget.AbsListView.RecyclerListener;
-import android.widget.ListView;
-
-import com.android.launcher3.R;
-
-import androidx.core.graphics.ColorUtils;
-
-/**
- * Utility class to scroll and highlight a list view item
- */
-public class ListViewHighlighter implements OnScrollListener, RecyclerListener,
- OnLayoutChangeListener {
-
- private final ListView mListView;
- private int mPosHighlight;
-
- private boolean mColorAnimated = false;
-
- public ListViewHighlighter(ListView listView, int posHighlight) {
- mListView = listView;
- mPosHighlight = posHighlight;
- mListView.setOnScrollListener(this);
- mListView.setRecyclerListener(this);
- mListView.addOnLayoutChangeListener(this);
-
- mListView.post(this::tryHighlight);
- }
-
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- mListView.post(this::tryHighlight);
- }
-
- private void tryHighlight() {
- if (mPosHighlight < 0 || mListView.getChildCount() == 0) {
- return;
- }
- if (!highlightIfVisible(mListView.getFirstVisiblePosition(),
- mListView.getLastVisiblePosition())) {
- mListView.smoothScrollToPosition(mPosHighlight);
- }
- }
-
- @Override
- public void onScrollStateChanged(AbsListView absListView, int i) { }
-
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem,
- int visibleItemCount, int totalItemCount) {
- highlightIfVisible(firstVisibleItem, firstVisibleItem + visibleItemCount - 1);
- }
-
- private boolean highlightIfVisible(int start, int end) {
- if (mPosHighlight < 0 || mListView.getChildCount() == 0) {
- return false;
- }
- if (start > mPosHighlight || mPosHighlight > end) {
- return false;
- }
- highlightView(mListView.getChildAt(mPosHighlight - start));
-
- // finish highlight
- mListView.setOnScrollListener(null);
- mListView.removeOnLayoutChangeListener(this);
-
- mPosHighlight = -1;
- return true;
- }
-
- @Override
- public void onMovedToScrapHeap(View view) {
- unhighlightView(view);
- }
-
- private void highlightView(View view) {
- if (Boolean.TRUE.equals(view.getTag(R.id.view_highlighted))) {
- // already highlighted
- } else {
- view.setTag(R.id.view_highlighted, true);
- view.setTag(R.id.view_unhighlight_background, view.getBackground());
- view.setBackground(getHighlightBackground());
- view.postDelayed(() -> {
- unhighlightView(view);
- }, 15000L);
- }
- }
-
- private void unhighlightView(View view) {
- if (Boolean.TRUE.equals(view.getTag(R.id.view_highlighted))) {
- Object background = view.getTag(R.id.view_unhighlight_background);
- if (background instanceof Drawable) {
- view.setBackground((Drawable) background);
- }
- view.setTag(R.id.view_unhighlight_background, null);
- view.setTag(R.id.view_highlighted, false);
- }
- }
-
- private ColorDrawable getHighlightBackground() {
- int color = ColorUtils.setAlphaComponent(Themes.getColorAccent(mListView.getContext()), 26);
- if (mColorAnimated) {
- return new ColorDrawable(color);
- }
- mColorAnimated = true;
- ColorDrawable bg = new ColorDrawable(Color.WHITE);
- ObjectAnimator anim = ObjectAnimator.ofInt(bg, "color", Color.WHITE, color);
- anim.setEvaluator(new ArgbEvaluator());
- anim.setDuration(200L);
- anim.setRepeatMode(ValueAnimator.REVERSE);
- anim.setRepeatCount(4);
- anim.start();
- return bg;
- }
-}
diff --git a/src/com/android/launcher3/views/ButtonPreference.java b/src/com/android/launcher3/views/ButtonPreference.java
deleted file mode 100644
index fdcf2ca..0000000
--- a/src/com/android/launcher3/views/ButtonPreference.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.views;
-
-import android.content.Context;
-import android.preference.Preference;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Extension of {@link Preference} which makes the widget layout clickable.
- *
- * @see #setWidgetLayoutResource(int)
- */
-public class ButtonPreference extends Preference {
-
- private boolean mWidgetFrameVisible = false;
-
- public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public ButtonPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public ButtonPreference(Context context) {
- super(context);
- }
-
- public void setWidgetFrameVisible(boolean isVisible) {
- if (mWidgetFrameVisible != isVisible) {
- mWidgetFrameVisible = isVisible;
- notifyChanged();
- }
- }
-
- @Override
- protected void onBindView(View view) {
- super.onBindView(view);
-
- ViewGroup widgetFrame = view.findViewById(android.R.id.widget_frame);
- if (widgetFrame != null) {
- widgetFrame.setVisibility(mWidgetFrameVisible ? View.VISIBLE : View.GONE);
- }
- }
-}
diff --git a/src_plugins/com/android/systemui/plugins/FirstScreenWidget.java b/src_plugins/com/android/systemui/plugins/FirstScreenWidget.java
new file mode 100644
index 0000000..8d7dd4b
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/FirstScreenWidget.java
@@ -0,0 +1,15 @@
+package com.android.systemui.plugins;
+
+import android.view.ViewGroup;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this interface to wrap the widget on the first home screen, e.g. to add new content.
+ */
+@ProvidesInterface(action = FirstScreenWidget.ACTION, version = FirstScreenWidget.VERSION)
+public interface FirstScreenWidget extends Plugin {
+ String ACTION = "com.android.systemui.action.PLUGIN_FIRST_SCREEN_WIDGET";
+ int VERSION = 1;
+
+ void onWidgetUpdated(ViewGroup widgetView);
+}