Set FLAG_IMMUTABLE flag on FirstScreenBroadcast PendingIntent. am: 6a7a7f6bad am: 3c3e3e0737
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/14651127
Change-Id: I309d1c1671abc7b2a035e69606d14e1f90c17f71
diff --git a/Android.mk b/Android.mk
index 9d113d9..78ea02a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -23,12 +23,7 @@
LOCAL_USE_AAPT2 := true
LOCAL_AAPT2_ONLY := true
LOCAL_MODULE_TAGS := optional
-
-ifneq (,$(wildcard frameworks/base))
- LOCAL_STATIC_JAVA_LIBRARIES:= PluginCoreLib
-else
- LOCAL_STATIC_JAVA_LIBRARIES:= libPluginCore
-endif
+LOCAL_STATIC_JAVA_LIBRARIES:= PluginCoreLib
LOCAL_SRC_FILES := \
$(call all-java-files-under, src_plugins)
@@ -151,11 +146,10 @@
LOCAL_AAPT2_ONLY := true
LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
ifneq (,$(wildcard frameworks/base))
- LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
LOCAL_PRIVATE_PLATFORM_APIS := true
else
- LOCAL_STATIC_JAVA_LIBRARIES := libSharedSystemUI libLauncherProtos
LOCAL_SDK_VERSION := system_current
LOCAL_MIN_SDK_VERSION := 26
endif
@@ -224,11 +218,10 @@
LOCAL_USE_AAPT2 := true
LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
ifneq (,$(wildcard frameworks/base))
- LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
LOCAL_PRIVATE_PLATFORM_APIS := true
else
- LOCAL_STATIC_JAVA_LIBRARIES := libSharedSystemUI libLauncherProtos
LOCAL_SDK_VERSION := system_current
LOCAL_MIN_SDK_VERSION := 26
endif
@@ -271,11 +264,10 @@
LOCAL_USE_AAPT2 := true
LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
ifneq (,$(wildcard frameworks/base))
- LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
LOCAL_PRIVATE_PLATFORM_APIS := true
else
- LOCAL_STATIC_JAVA_LIBRARIES := libSharedSystemUI libLauncherProtos
LOCAL_SDK_VERSION := system_current
LOCAL_MIN_SDK_VERSION := 26
endif
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 5318a12..555cc73 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -43,6 +43,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<!--
@@ -168,7 +169,7 @@
<activity
android:name="com.android.launcher3.settings.SettingsActivity"
android:label="@string/settings_button_text"
- android:theme="@android:style/Theme.DeviceDefault.Settings"
+ android:theme="@style/HomeSettingsTheme"
android:autoRemoveFromRecents="true">
<intent-filter>
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
diff --git a/OWNERS b/OWNERS
index 6c1273f..538ca33 100644
--- a/OWNERS
+++ b/OWNERS
@@ -10,3 +10,6 @@
sunnygoyal@google.com
twickham@google.com
winsonc@google.com
+
+per-file FeatureFlags.java = sunnygoyal@google.com, adamcohen@google.com
+per-file BaseFlags.java = sunnygoyal@google.com, adamcohen@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..f3db20e
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,2 @@
+[Hook Scripts]
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
diff --git a/SecondaryDisplayLauncher/res/values-ar/strings.xml b/SecondaryDisplayLauncher/res/values-ar/strings.xml
index ba81c11..aa34c7d 100644
--- a/SecondaryDisplayLauncher/res/values-ar/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-ar/strings.xml
@@ -21,5 +21,5 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="couldnt_launch" msgid="7873588052226763866">"تعذَّر تشغيل النشاط."</string>
<string name="add_app_shortcut" msgid="2756755330707509435">"إضافة اختصار التطبيق"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"تعيين الخلفية"</string>
+ <string name="set_wallpaper" msgid="6475195450505435904">"ضبط الخلفية"</string>
</resources>
diff --git a/SecondaryDisplayLauncher/res/values-en-rCA/strings.xml b/SecondaryDisplayLauncher/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..8d8c419
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-en-rCA/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="couldnt_launch" msgid="7873588052226763866">"Couldn\'t launch the activity"</string>
+ <string name="add_app_shortcut" msgid="2756755330707509435">"Add app shortcut"</string>
+ <string name="set_wallpaper" msgid="6475195450505435904">"Set wallpaper"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-en-rXC/strings.xml b/SecondaryDisplayLauncher/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..da69193
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-en-rXC/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="couldnt_launch" msgid="7873588052226763866">"Couldn\'t launch the activity"</string>
+ <string name="add_app_shortcut" msgid="2756755330707509435">"Add app shortcut"</string>
+ <string name="set_wallpaper" msgid="6475195450505435904">"Set wallpaper"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml b/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml
index bf76f29..c02fe2c 100644
--- a/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml
@@ -21,5 +21,5 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="couldnt_launch" msgid="7873588052226763866">"無法啟動活動"</string>
<string name="add_app_shortcut" msgid="2756755330707509435">"新增應用程式捷徑"</string>
- <string name="set_wallpaper" msgid="6475195450505435904">"設定桌布"</string>
+ <string name="set_wallpaper" msgid="6475195450505435904">"套用桌布"</string>
</resources>
diff --git a/go/quickstep/res/values/override.xml b/go/quickstep/res/values/override.xml
index 7636fb3..bb267a3 100644
--- a/go/quickstep/res/values/override.xml
+++ b/go/quickstep/res/values/override.xml
@@ -22,5 +22,7 @@
<string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
<string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
+
+ <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
</resources>
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index d0cfcf9..212ce9b 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -32,6 +32,7 @@
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
+import android.content.Context;
import android.view.View;
import com.android.launcher3.DeviceProfile;
@@ -115,10 +116,10 @@
}
public static float getDefaultSwipeHeight(Launcher launcher) {
- return getDefaultSwipeHeight(launcher.getDeviceProfile());
+ return getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
}
- public static float getDefaultSwipeHeight(DeviceProfile dp) {
+ public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) {
return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
}
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeStatesTouchController.java b/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeStatesTouchController.java
index 1ccd7d7..66aec40 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeStatesTouchController.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeStatesTouchController.java
@@ -64,7 +64,7 @@
}
@Override
- protected int getLogContainerTypeForNormalState() {
+ protected int getLogContainerTypeForNormalState(MotionEvent ev) {
return LauncherLogProto.ContainerType.WORKSPACE;
}
}
diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index fe159b5..92900f2 100644
--- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -25,6 +25,7 @@
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.app.ActivityOptions;
+import android.content.Context;
import android.os.Handler;
import android.util.Log;
@@ -151,7 +152,7 @@
}
@Override
- public ActivityOptions toActivityOptions(Handler handler, long duration) {
+ public ActivityOptions toActivityOptions(Handler handler, long duration, Context context) {
LauncherAnimationRunner runner = new LauncherAnimationRunner(handler,
false /* startAtFrontOfQueue */) {
@@ -165,7 +166,7 @@
);
return;
}
- result.setAnimation(createWindowAnimation(targetCompats));
+ result.setAnimation(createWindowAnimation(targetCompats), context);
}
};
return ActivityOptionsCompat.makeRemoteAnimation(
diff --git a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 0fa3d86..216972c 100644
--- a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -15,8 +15,8 @@
*/
package com.android.quickstep;
-import static com.android.systemui.shared.system.ActivityManagerWrapper
- .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.annotation.TargetApi;
import android.content.Context;
@@ -25,7 +25,6 @@
import android.view.ViewConfiguration;
import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
@@ -43,7 +42,6 @@
private final Context mContext;
private final ActivityManagerWrapper mAM;
private final RecentsModel mRecentsModel;
- private final MainThreadExecutor mMainThreadExecutor;
private final OverviewComponentObserver mOverviewComponentObserver;
private long mLastToggleTime;
@@ -51,7 +49,6 @@
public OverviewCommandHelper(Context context, OverviewComponentObserver observer) {
mContext = context;
mAM = ActivityManagerWrapper.getInstance();
- mMainThreadExecutor = new MainThreadExecutor();
mRecentsModel = RecentsModel.INSTANCE.get(mContext);
mOverviewComponentObserver = observer;
}
@@ -63,19 +60,19 @@
}
mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- mMainThreadExecutor.execute(new RecentsActivityCommand<>());
+ MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
}
public void onOverviewShown(boolean triggeredFromAltTab) {
- mMainThreadExecutor.execute(new ShowRecentsCommand());
+ MAIN_EXECUTOR.execute(new ShowRecentsCommand());
}
public void onOverviewHidden() {
- mMainThreadExecutor.execute(new HideRecentsCommand());
+ MAIN_EXECUTOR.execute(new HideRecentsCommand());
}
public void onTip(int actionType, int viewType) {
- mMainThreadExecutor.execute(() ->
+ MAIN_EXECUTOR.execute(() ->
UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType));
}
@@ -161,7 +158,7 @@
// Otherwise, start overview.
mListener = mHelper.createActivityInitListener(provider::onActivityReady);
mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
- provider, mContext, mMainThreadExecutor.getHandler(),
+ provider, mContext, MAIN_EXECUTOR.getHandler(),
provider.getRecentsLaunchDuration());
}
diff --git a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 900b94e..19dd82f 100644
--- a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -180,4 +180,8 @@
}
return mMyBinder;
}
+
+ public static boolean isInitialized() {
+ return true;
+ }
}
diff --git a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java
new file mode 100644
index 0000000..fb89013
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 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.quickstep.util;
+
+import com.android.launcher3.Launcher;
+
+/** Empty class, only exists so that l3goWithQuickstepIconRecentsDebug compiles. */
+public class ShelfPeekAnim {
+ public ShelfPeekAnim(Launcher launcher) {
+ }
+
+ public enum ShelfAnimState {
+ }
+
+ public boolean isPeeking() {
+ return false;
+ }
+}
diff --git a/go/src/com/android/launcher3/model/LoaderResults.java b/go/src/com/android/launcher3/model/LoaderResults.java
index b82f362..26c3313 100644
--- a/go/src/com/android/launcher3/model/LoaderResults.java
+++ b/go/src/com/android/launcher3/model/LoaderResults.java
@@ -16,9 +16,8 @@
package com.android.launcher3.model;
-import com.android.launcher3.AllAppsList;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
import java.lang.ref.WeakReference;
diff --git a/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
index 1e44910..42b1194 100644
--- a/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -25,39 +25,25 @@
import android.os.UserHandle;
import com.android.launcher3.ItemInfo;
+import com.android.launcher3.notification.NotificationKeyData;
-import java.util.Collections;
+import java.util.ArrayList;
import java.util.List;
/**
* Performs operations related to deep shortcuts, such as querying for them, pinning them, etc.
*/
public class DeepShortcutManager {
- private static DeepShortcutManager sInstance;
- private static final Object sInstanceLock = new Object();
+
+ private static final DeepShortcutManager sInstance = new DeepShortcutManager();
public static DeepShortcutManager getInstance(Context context) {
- synchronized (sInstanceLock) {
- if (sInstance == null) {
- sInstance = new DeepShortcutManager(context.getApplicationContext());
- }
- return sInstance;
- }
+ return sInstance;
}
- private DeepShortcutManager(Context context) {
- }
+ private final QueryResult mFailure = new QueryResult();
- public static boolean supportsShortcuts(ItemInfo info) {
- return false;
- }
-
- public boolean wasLastCallSuccess() {
- return false;
- }
-
- public void onShortcutsChanged(List<ShortcutInfo> shortcuts) {
- }
+ private DeepShortcutManager() { }
/**
* Queries for the shortcuts with the package name and provided ids.
@@ -65,18 +51,18 @@
* This method is intended to get the full details for shortcuts when they are added or updated,
* because we only get "key" fields in onShortcutsChanged().
*/
- public List<ShortcutInfo> queryForFullDetails(String packageName,
+ public QueryResult queryForFullDetails(String packageName,
List<String> shortcutIds, UserHandle user) {
- return Collections.emptyList();
+ return mFailure;
}
/**
* Gets all the manifest and dynamic shortcuts associated with the given package and user,
* to be displayed in the shortcuts container on long press.
*/
- public List<ShortcutInfo> queryForShortcutsContainer(ComponentName activity,
+ public QueryResult queryForShortcutsContainer(ComponentName activity,
UserHandle user) {
- return Collections.emptyList();
+ return mFailure;
}
/**
@@ -106,20 +92,28 @@
*
* If packageName is null, returns all pinned shortcuts regardless of package.
*/
- public List<ShortcutInfo> queryForPinnedShortcuts(String packageName, UserHandle user) {
- return Collections.emptyList();
+ public QueryResult queryForPinnedShortcuts(String packageName, UserHandle user) {
+ return mFailure;
}
- public List<ShortcutInfo> queryForPinnedShortcuts(String packageName,
+ public QueryResult queryForPinnedShortcuts(String packageName,
List<String> shortcutIds, UserHandle user) {
- return Collections.emptyList();
+ return mFailure;
}
- public List<ShortcutInfo> queryForAllShortcuts(UserHandle user) {
- return Collections.emptyList();
+ public QueryResult queryForAllShortcuts(UserHandle user) {
+ return mFailure;
}
public boolean hasHostPermission() {
return false;
}
+
+
+ public static class QueryResult extends ArrayList<ShortcutInfo> {
+
+ public boolean wasSuccess() {
+ return true;
+ }
+ }
}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index 60320d6..5c4f37c 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -23,6 +23,8 @@
import android.os.Process;
import android.os.UserHandle;
+import androidx.annotation.NonNull;
+
/**
* This class will be moved to androidx library. There shouldn't be any dependency outside
* this package.
@@ -34,6 +36,8 @@
static final boolean ATLEAST_OREO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
static final boolean ATLEAST_P = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
+ private static final float ICON_BADGE_SCALE = 0.444f;
+
private final Rect mOldBounds = new Rect();
protected final Context mContext;
private final Canvas mCanvas;
@@ -154,7 +158,7 @@
* @param scale returns the scale result from normalization
* @return a bitmap suitable for disaplaying as an icon at various system UIs.
*/
- public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
+ public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon, UserHandle user,
boolean shrinkNonAdaptiveIcons, boolean isInstantApp, float[] scale) {
if (scale == null) {
scale = new float[1];
@@ -204,8 +208,11 @@
mDisableColorExtractor = true;
}
- private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, boolean shrinkNonAdaptiveIcons,
- RectF outIconBounds, float[] outScale) {
+ private Drawable normalizeAndWrapToAdaptiveIcon(@NonNull Drawable icon,
+ boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale) {
+ if (icon == null) {
+ return null;
+ }
float scale = 1f;
if (shrinkNonAdaptiveIcons && ATLEAST_OREO) {
@@ -247,7 +254,7 @@
* Adds the {@param badge} on top of {@param target} using the badge dimensions.
*/
public void badgeWithDrawable(Canvas target, Drawable badge) {
- int badgeSize = mContext.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
+ int badgeSize = getBadgeSizeForIconSize(mIconBitmapSize);
badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
mIconBitmapSize, mIconBitmapSize);
badge.draw(target);
@@ -261,7 +268,7 @@
* @param icon drawable that should be flattened to a bitmap
* @param scale the scale to apply before drawing {@param icon} on the canvas
*/
- public Bitmap createIconBitmap(Drawable icon, float scale, int size) {
+ public Bitmap createIconBitmap(@NonNull Drawable icon, float scale, int size) {
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
if (icon == null) {
return bitmap;
@@ -328,6 +335,13 @@
}
/**
+ * Returns the correct badge size given an icon size
+ */
+ public static int getBadgeSizeForIconSize(int iconSize) {
+ return (int) (ICON_BADGE_SCALE * iconSize);
+ }
+
+ /**
* An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
* This allows the badging to be done based on the action bitmap size rather than
* the scaled bitmap size.
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
index d84633d..a886c0a 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
@@ -36,12 +36,16 @@
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
+import android.os.LocaleList;
import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.BitmapRenderer;
@@ -57,8 +61,6 @@
import java.util.Set;
import java.util.function.Supplier;
-import androidx.annotation.NonNull;
-
public abstract class BaseIconCache {
private static final String TAG = "BaseIconCache";
@@ -84,6 +86,7 @@
protected int mIconDpi;
protected IconDB mIconDb;
+ protected LocaleList mLocaleList = LocaleList.getEmptyLocaleList();
protected String mSystemState = "";
private final String mDbFileName;
@@ -227,12 +230,12 @@
/**
* Refreshes the system state definition used to check the validity of the cache. It
- * incorporates all the properties that can affect the cache like locale and system-version.
+ * incorporates all the properties that can affect the cache like the list of enabled locale
+ * and system-version.
*/
private void updateSystemState() {
- final String locale =
- mContext.getResources().getConfiguration().getLocales().toLanguageTags();
- mSystemState = locale + "," + Build.VERSION.SDK_INT;
+ mLocaleList = mContext.getResources().getConfiguration().getLocales();
+ mSystemState = mLocaleList.toLanguageTags() + "," + Build.VERSION.SDK_INT;
}
protected String getIconSystemState(String packageName) {
@@ -264,12 +267,16 @@
entry = new CacheEntry();
cachingLogic.loadIcon(mContext, object, entry);
}
+ // Icon can't be loaded from cachingLogic, which implies alternative icon was loaded
+ // (e.g. fallback icon, default icon). So we drop here since there's no point in caching
+ // an empty entry.
+ if (entry.icon == null) return;
entry.title = cachingLogic.getLabel(object);
entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user);
- mCache.put(key, entry);
+ if (cachingLogic.addToMemCache()) mCache.put(key, entry);
ContentValues values = newContentValues(entry, entry.title.toString(),
- componentName.getPackageName());
+ componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList));
addIconToDB(values, componentName, info, userSerial);
}
@@ -305,20 +312,12 @@
@NonNull ComponentName componentName, @NonNull UserHandle user,
@NonNull Supplier<T> infoProvider, @NonNull CachingLogic<T> cachingLogic,
boolean usePackageIcon, boolean useLowResIcon) {
- return cacheLocked(componentName, user, infoProvider, cachingLogic, usePackageIcon,
- useLowResIcon, true);
- }
-
- protected <T> CacheEntry cacheLocked(
- @NonNull ComponentName componentName, @NonNull UserHandle user,
- @NonNull Supplier<T> infoProvider, @NonNull CachingLogic<T> cachingLogic,
- boolean usePackageIcon, boolean useLowResIcon, boolean addToMemCache) {
assertWorkerThread();
ComponentKey cacheKey = new ComponentKey(componentName, user);
CacheEntry entry = mCache.get(cacheKey);
if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
entry = new CacheEntry();
- if (addToMemCache) {
+ if (cachingLogic.addToMemCache()) {
mCache.put(cacheKey, entry);
}
@@ -445,7 +444,7 @@
// Add the icon in the DB here, since these do not get written during
// package updates.
ContentValues values = newContentValues(
- iconInfo, entry.title.toString(), packageName);
+ iconInfo, entry.title.toString(), packageName, null);
addIconToDB(values, cacheKey.componentName, info, getSerialNumberForUser(user));
} catch (NameNotFoundException e) {
@@ -504,23 +503,35 @@
return false;
}
- static final class IconDB extends SQLiteCacheHelper {
- private final static int RELEASE_VERSION = 26;
+ /**
+ * Returns a cursor for an arbitrary query to the cache db
+ */
+ public synchronized Cursor queryCacheDb(String[] columns, String selection,
+ String[] selectionArgs) {
+ return mIconDb.query(columns, selection, selectionArgs);
+ }
- public final static String TABLE_NAME = "icons";
- public final static String COLUMN_ROWID = "rowid";
- public final static String COLUMN_COMPONENT = "componentName";
- public final static String COLUMN_USER = "profileId";
- public final static String COLUMN_LAST_UPDATED = "lastUpdated";
- public final static String COLUMN_VERSION = "version";
- public final static String COLUMN_ICON = "icon";
- public final static String COLUMN_ICON_COLOR = "icon_color";
- public final static String COLUMN_LABEL = "label";
- public final static String COLUMN_SYSTEM_STATE = "system_state";
+ /**
+ * Cache class to store the actual entries on disk
+ */
+ public static final class IconDB extends SQLiteCacheHelper {
+ private static final int RELEASE_VERSION = 27;
- public final static String[] COLUMNS_HIGH_RES = new String[] {
+ public static final String TABLE_NAME = "icons";
+ public static final String COLUMN_ROWID = "rowid";
+ public static final String COLUMN_COMPONENT = "componentName";
+ public static final String COLUMN_USER = "profileId";
+ public static final String COLUMN_LAST_UPDATED = "lastUpdated";
+ public static final String COLUMN_VERSION = "version";
+ public static final String COLUMN_ICON = "icon";
+ public static final String COLUMN_ICON_COLOR = "icon_color";
+ public static final String COLUMN_LABEL = "label";
+ public static final String COLUMN_SYSTEM_STATE = "system_state";
+ public static final String COLUMN_KEYWORDS = "keywords";
+
+ public static final String[] COLUMNS_HIGH_RES = new String[] {
IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON };
- public final static String[] COLUMNS_LOW_RES = new String[] {
+ public static final String[] COLUMNS_LOW_RES = new String[] {
IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL };
public IconDB(Context context, String dbFileName, int iconPixelSize) {
@@ -529,21 +540,23 @@
@Override
protected void onCreateTable(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
- COLUMN_COMPONENT + " TEXT NOT NULL, " +
- COLUMN_USER + " INTEGER NOT NULL, " +
- COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " +
- COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " +
- COLUMN_ICON + " BLOB, " +
- COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, " +
- COLUMN_LABEL + " TEXT, " +
- COLUMN_SYSTEM_STATE + " TEXT, " +
- "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " +
- ");");
+ db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
+ + COLUMN_COMPONENT + " TEXT NOT NULL, "
+ + COLUMN_USER + " INTEGER NOT NULL, "
+ + COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, "
+ + COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, "
+ + COLUMN_ICON + " BLOB, "
+ + COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, "
+ + COLUMN_LABEL + " TEXT, "
+ + COLUMN_SYSTEM_STATE + " TEXT, "
+ + COLUMN_KEYWORDS + " TEXT, "
+ + "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") "
+ + ");");
}
}
- private ContentValues newContentValues(BitmapInfo bitmapInfo, String label, String packageName) {
+ private ContentValues newContentValues(BitmapInfo bitmapInfo, String label,
+ String packageName, @Nullable String keywords) {
ContentValues values = new ContentValues();
values.put(IconDB.COLUMN_ICON,
bitmapInfo.isLowRes() ? null : GraphicsUtils.flattenBitmap(bitmapInfo.icon));
@@ -551,7 +564,7 @@
values.put(IconDB.COLUMN_LABEL, label);
values.put(IconDB.COLUMN_SYSTEM_STATE, getIconSystemState(packageName));
-
+ values.put(IconDB.COLUMN_KEYWORDS, keywords);
return values;
}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
index addb51f..e40a9c2 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
@@ -17,8 +17,11 @@
import android.content.ComponentName;
import android.content.Context;
+import android.os.LocaleList;
import android.os.UserHandle;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.icons.BitmapInfo;
public interface CachingLogic<T> {
@@ -30,4 +33,19 @@
CharSequence getLabel(T object);
void loadIcon(Context context, T object, BitmapInfo target);
+
+ /**
+ * Provides a option list of keywords to associate with this object
+ */
+ @Nullable
+ default String getKeywords(T object, LocaleList localeList) {
+ return null;
+ }
+
+ /**
+ * Returns true the object should be added to mem cache; otherwise returns false.
+ */
+ default boolean addToMemCache() {
+ return true;
+ }
}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
index 3c71bd0..8224966 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
@@ -171,8 +171,8 @@
long updateTime = c.getLong(indexLastUpdate);
int version = c.getInt(indexVersion);
T app = componentMap.remove(component);
- if (version == info.versionCode && updateTime == info.lastUpdateTime &&
- TextUtils.equals(c.getString(systemStateIndex),
+ if (version == info.versionCode && updateTime == info.lastUpdateTime
+ && TextUtils.equals(c.getString(systemStateIndex),
mIconCache.getIconSystemState(info.packageName))) {
if (mFilterMode == MODE_CLEAR_VALID_ITEMS) {
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 49fd436..fd36d4b 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -118,6 +118,7 @@
APP_USAGE_SETTINGS = 18;
BACK_GESTURE = 19;
UNDO = 20;
+ DISMISS_PREDICTION = 21;
}
enum TipType {
@@ -136,7 +137,8 @@
AUTOMATED = 1;
COMMAND = 2;
TIP = 3;
- // SOFT_KEYBOARD, HARD_KEYBOARD, ASSIST
+ SOFT_KEYBOARD = 4;
+ // HARD_KEYBOARD, ASSIST
}
enum Touch {
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 332e0fa..826a275 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -23,6 +23,7 @@
package="com.android.launcher3" >
<uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
+ <uses-permission android:name="android.permission.VIBRATE" />
<application
android:backupAgent="com.android.launcher3.LauncherBackupAgent"
@@ -87,6 +88,7 @@
<activity android:name="com.android.quickstep.LockScreenRecentsActivity"
android:theme="@android:style/Theme.NoDisplay"
android:showOnLockScreen="true"
+ android:taskAffinity="${packageName}.locktask"
android:directBootAware="true" />
</application>
diff --git a/quickstep/recents_ui_overrides/res/values/override.xml b/quickstep/recents_ui_overrides/res/values/override.xml
index 1ddd3f5..ed3ba92 100644
--- a/quickstep/recents_ui_overrides/res/values/override.xml
+++ b/quickstep/recents_ui_overrides/res/values/override.xml
@@ -24,5 +24,7 @@
<string name="app_launch_tracker_class" translatable="false">com.android.launcher3.appprediction.PredictionAppTracker</string>
<string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
+
+ <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension</string>
</resources>
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 371161e..114fd8e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -17,31 +17,39 @@
package com.android.launcher3;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
-import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS;
-import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
-import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.SpringObjectAnimator;
+import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.quickstep.util.ClipAnimationHelper;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -56,6 +64,9 @@
public static final int INDEX_SHELF_ANIM = 0;
public static final int INDEX_RECENTS_FADE_ANIM = 1;
public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = 2;
+ public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM = 3;
+
+ public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
public LauncherAppTransitionManagerImpl(Context context) {
super(context);
@@ -144,20 +155,66 @@
@Override
public int getStateElementAnimationsCount() {
- return 3;
+ return 4;
}
@Override
public Animator createStateElementAnimation(int index, float... values) {
switch (index) {
- case INDEX_SHELF_ANIM:
- return mLauncher.getAllAppsController().createSpringAnimation(values);
+ case INDEX_SHELF_ANIM: {
+ AllAppsTransitionController aatc = mLauncher.getAllAppsController();
+ Animator springAnim = aatc.createSpringAnimation(values);
+
+ if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+ // Translate hotseat with the shelf until reaching overview.
+ float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
+ ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mLauncher);
+ float shiftRange = aatc.getShiftRange();
+ if (values.length == 1) {
+ values = new float[] {aatc.getProgress(), values[0]};
+ }
+ ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values);
+ hotseatAnim.addUpdateListener(anim -> {
+ float progress = (Float) anim.getAnimatedValue();
+ if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
+ float hotseatShift = (progress - overviewProgress) * shiftRange;
+ mLauncher.getHotseat().setTranslationY(hotseatShift + sat.translationY);
+ }
+ });
+ hotseatAnim.setInterpolator(LINEAR);
+ hotseatAnim.setDuration(springAnim.getDuration());
+
+ AnimatorSet anim = new AnimatorSet();
+ anim.play(hotseatAnim);
+ anim.play(springAnim);
+ return anim;
+ }
+
+ return springAnim;
+ }
case INDEX_RECENTS_FADE_ANIM:
return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
RecentsView.CONTENT_ALPHA, values);
case INDEX_RECENTS_TRANSLATE_X_ANIM:
- return new SpringObjectAnimator<>(mLauncher.getOverviewPanel(),
- VIEW_TRANSLATE_X, MIN_VISIBLE_CHANGE_PIXELS, 0.8f, 250, values);
+ return new SpringAnimationBuilder<>(mLauncher.getOverviewPanel(), VIEW_TRANSLATE_X)
+ .setDampingRatio(0.8f)
+ .setStiffness(250)
+ .setValues(values)
+ .build(mLauncher);
+ case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
+ AnimatorSetBuilder builder = new AnimatorSetBuilder();
+ builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
+ builder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
+ if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+ builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
+ builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
+ }
+ LauncherStateManager stateManager = mLauncher.getStateManager();
+ return stateManager.createAtomicAnimation(
+ stateManager.getCurrentStableState(), OVERVIEW, builder,
+ ANIM_ALL, ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW);
+ }
+
default:
return super.createStateElementAnimation(index, values);
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
index 311db21..425fb13 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -31,6 +31,9 @@
import android.view.View;
import android.view.animation.Interpolator;
+import androidx.annotation.ColorInt;
+import androidx.core.content.ContextCompat;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
@@ -41,9 +44,6 @@
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.util.Themes;
-import androidx.annotation.ColorInt;
-import androidx.core.content.ContextCompat;
-
/**
* A view which shows a horizontal divider
*/
@@ -288,10 +288,10 @@
}
@Override
- public void setContentVisibility(boolean hasHeaderExtra, boolean hasContent,
- PropertySetter setter, Interpolator fadeInterpolator) {
+ public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
+ PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
// Don't use setViewAlpha as we want to control the visibility ourselves.
- setter.setFloat(this, ALPHA, hasContent ? 1 : 0, fadeInterpolator);
+ setter.setFloat(this, ALPHA, hasAllAppsContent ? 1 : 0, allAppsFade);
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
index 4ecc39c..65e69b6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
@@ -17,6 +17,8 @@
import static android.content.pm.PackageManager.MATCH_INSTANT;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -30,8 +32,12 @@
import android.util.ArrayMap;
import android.util.Log;
+import androidx.annotation.MainThread;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherIcons;
@@ -45,11 +51,6 @@
import java.util.List;
import java.util.Map;
-import androidx.annotation.MainThread;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-
/**
* Utility class which loads and caches predicted items like instant apps and shortcuts, before
* they can be displayed on the UI
@@ -77,7 +78,7 @@
public DynamicItemCache(Context context, Runnable onUpdateCallback) {
mContext = context;
- mWorker = new Handler(LauncherModel.getWorkerLooper(), this::handleWorkerMessage);
+ mWorker = new Handler(MODEL_EXECUTOR.getLooper(), this::handleWorkerMessage);
mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage);
mInstantAppResolver = InstantAppResolver.newInstance(context);
mOnUpdateCallback = onUpdateCallback;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
index 8f1282d..4c7943b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,6 +16,7 @@
package com.android.launcher3.appprediction;
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.annotation.TargetApi;
import android.app.prediction.AppPredictionContext;
@@ -34,19 +35,25 @@
import android.util.Log;
import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
import com.android.launcher3.model.AppLaunchTracker;
-import com.android.launcher3.util.UiThreadHelper;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.systemui.plugins.AppLaunchEventsPlugin;
+import com.android.systemui.plugins.PluginListener;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
+import java.util.ArrayList;
+import java.util.List;
/**
* Subclass of app tracker which publishes the data to the prediction engine and gets back results.
*/
@TargetApi(Build.VERSION_CODES.Q)
-public class PredictionAppTracker extends AppLaunchTracker {
+public class PredictionAppTracker extends AppLaunchTracker
+ implements PluginListener<AppLaunchEventsPlugin> {
private static final String TAG = "PredictionAppTracker";
private static final boolean DBG = false;
@@ -58,6 +65,7 @@
protected final Context mContext;
private final Handler mMessageHandler;
+ private final List<AppLaunchEventsPlugin> mAppLaunchEventsPluginsList;
// Accessed only on worker thread
private AppPredictor mHomeAppPredictor;
@@ -65,10 +73,14 @@
public PredictionAppTracker(Context context) {
mContext = context;
- mMessageHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleMessage);
+ mMessageHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessage);
InvariantDeviceProfile.INSTANCE.get(mContext).addOnChangeListener(this::onIdpChanged);
mMessageHandler.sendEmptyMessage(MSG_INIT);
+
+ mAppLaunchEventsPluginsList = new ArrayList<>();
+ PluginManagerWrapper.INSTANCE.get(context)
+ .addPluginListener(this, AppLaunchEventsPlugin.class, true);
}
@UiThread
@@ -95,6 +107,10 @@
private AppPredictor createPredictor(Client client, int count) {
AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
+ if (apm == null) {
+ return null;
+ }
+
AppPredictor predictor = apm.createAppPredictionSession(
new AppPredictionContext.Builder(mContext)
.setUiSurface(client.id)
@@ -112,7 +128,7 @@
*/
@WorkerThread
@Nullable
- public Bundle getAppPredictionContextExtras(Client client){
+ public Bundle getAppPredictionContextExtras(Client client) {
return null;
}
@@ -124,7 +140,7 @@
destroy();
// Initialize the clients
- int count = InvariantDeviceProfile.INSTANCE.get(mContext).numColumns;
+ int count = InvariantDeviceProfile.INSTANCE.get(mContext).numAllAppsColumns;
mHomeAppPredictor = createPredictor(Client.HOME, count);
mRecentsOverviewPredictor = createPredictor(Client.OVERVIEW, count);
return true;
@@ -163,37 +179,98 @@
if (DBG) {
Log.d(TAG, String.format("Sent immediate message to update %s", client));
}
+
+ // Relay onReturnedToHome to every plugin.
+ mAppLaunchEventsPluginsList.forEach(AppLaunchEventsPlugin::onReturnedToHome);
}
@Override
@UiThread
public void onStartShortcut(String packageName, String shortcutId, UserHandle user,
- String container) {
+ String container) {
// TODO: Use the full shortcut info
- AppTarget target = new AppTarget
- .Builder(new AppTargetId("shortcut:" + shortcutId), packageName, user)
- .setClassName(shortcutId)
- .build();
+ AppTarget target = new AppTarget.Builder(
+ new AppTargetId("shortcut:" + shortcutId), packageName, user)
+ .setClassName(shortcutId)
+ .build();
+
sendLaunch(target, container);
+
+ // Relay onStartShortcut info to every connected plugin.
+ mAppLaunchEventsPluginsList
+ .forEach(plugin -> plugin.onStartShortcut(
+ packageName,
+ shortcutId,
+ user,
+ container != null ? container : CONTAINER_DEFAULT)
+ );
+
}
@Override
@UiThread
public void onStartApp(ComponentName cn, UserHandle user, String container) {
if (cn != null) {
- AppTarget target = new AppTarget
- .Builder(new AppTargetId("app:" + cn), cn.getPackageName(), user)
- .setClassName(cn.getClassName())
- .build();
+ AppTarget target = new AppTarget.Builder(
+ new AppTargetId("app:" + cn), cn.getPackageName(), user)
+ .setClassName(cn.getClassName())
+ .build();
sendLaunch(target, container);
+
+ // Relay onStartApp to every connected plugin.
+ mAppLaunchEventsPluginsList
+ .forEach(plugin -> plugin.onStartApp(
+ cn,
+ user,
+ container != null ? container : CONTAINER_DEFAULT)
+ );
}
}
+ @Override
+ @UiThread
+ public void onDismissApp(ComponentName cn, UserHandle user, String container) {
+ if (cn == null) return;
+ AppTarget target = new AppTarget.Builder(
+ new AppTargetId("app: " + cn), cn.getPackageName(), user)
+ .setClassName(cn.getClassName())
+ .build();
+ sendDismiss(target, container);
+
+ // Relay onDismissApp to every connected plugin.
+ mAppLaunchEventsPluginsList
+ .forEach(plugin -> plugin.onDismissApp(
+ cn,
+ user,
+ container != null ? container : CONTAINER_DEFAULT)
+ );
+ }
+
+ @UiThread
+ private void sendEvent(AppTarget target, String container, int eventId) {
+ AppTargetEvent event = new AppTargetEvent.Builder(target, eventId)
+ .setLaunchLocation(container == null ? CONTAINER_DEFAULT : container)
+ .build();
+ Message.obtain(mMessageHandler, MSG_LAUNCH, event).sendToTarget();
+ }
+
@UiThread
private void sendLaunch(AppTarget target, String container) {
- AppTargetEvent event = new AppTargetEvent.Builder(target, AppTargetEvent.ACTION_LAUNCH)
- .setLaunchLocation(container == null ? CONTAINER_DEFAULT : container)
- .build();
- Message.obtain(mMessageHandler, MSG_LAUNCH, event).sendToTarget();
+ sendEvent(target, container, AppTargetEvent.ACTION_LAUNCH);
+ }
+
+ @UiThread
+ private void sendDismiss(AppTarget target, String container) {
+ sendEvent(target, container, AppTargetEvent.ACTION_DISMISS);
+ }
+
+ @Override
+ public void onPluginConnected(AppLaunchEventsPlugin appLaunchEventsPlugin, Context context) {
+ mAppLaunchEventsPluginsList.add(appLaunchEventsPlugin);
+ }
+
+ @Override
+ public void onPluginDisconnected(AppLaunchEventsPlugin appLaunchEventsPlugin) {
+ mAppLaunchEventsPluginsList.remove(appLaunchEventsPlugin);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
index cb5cbdd..f82af62 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -32,6 +32,9 @@
import android.view.animation.Interpolator;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.AppInfo;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
@@ -40,6 +43,7 @@
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.allapps.AllAppsStore;
@@ -62,9 +66,6 @@
import java.util.Collections;
import java.util.List;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
@TargetApi(Build.VERSION_CODES.P)
public class PredictionRowView extends LinearLayout implements
LogContainerProvider, OnDeviceProfileChangeListener, FloatingHeaderRow {
@@ -80,7 +81,7 @@
@Override
public Integer get(PredictionRowView view) {
- return view.mIconCurrentTextAlpha;
+ return view.mIconLastSetTextAlpha;
}
};
@@ -92,7 +93,7 @@
private final Launcher mLauncher;
private final PredictionUiStateManager mPredictionUiStateManager;
- private final int mNumPredictedAppsPerRow;
+ private int mNumPredictedAppsPerRow;
// The set of predicted app component names
private final List<ComponentKeyMapper> mPredictedAppComponents = new ArrayList<>();
@@ -103,6 +104,8 @@
private final int mIconTextColor;
private final int mIconFullTextAlpha;
+ private int mIconLastSetTextAlpha;
+ // Might use mIconFullTextAlpha instead of mIconLastSetTextAlpha if we are translucent.
private int mIconCurrentTextAlpha;
private FloatingHeaderView mParent;
@@ -126,7 +129,7 @@
mFocusHelper = new SimpleFocusIndicatorHelper(this);
- mNumPredictedAppsPerRow = LauncherAppState.getIDP(context).numColumns;
+ mNumPredictedAppsPerRow = LauncherAppState.getIDP(context).numAllAppsColumns;
mLauncher = Launcher.getLauncher(context);
mLauncher.addOnDeviceProfileChangeListener(this);
@@ -224,6 +227,7 @@
@Override
public void onDeviceProfileChanged(DeviceProfile dp) {
+ mNumPredictedAppsPerRow = dp.inv.numAllAppsColumns;
removeAllViews();
applyPredictionApps();
}
@@ -272,14 +276,14 @@
boolean predictionsEnabled = predictionCount > 0;
if (predictionsEnabled != mPredictionsEnabled) {
mPredictionsEnabled = predictionsEnabled;
- mLauncher.reapplyUi();
+ mLauncher.reapplyUi(false /* cancelCurrentAnimation */);
updateVisibility();
}
mParent.onHeightUpdated();
}
private List<ItemInfoWithIcon> processPredictedAppComponents(List<ComponentKeyMapper> components) {
- if (getAppsStore().getApps().isEmpty()) {
+ if (getAppsStore().getApps().length == 0) {
// Apps have not been bound yet.
return Collections.emptyList();
}
@@ -288,7 +292,9 @@
for (ComponentKeyMapper mapper : components) {
ItemInfoWithIcon info = mapper.getApp(getAppsStore());
if (info != null) {
- predictedApps.add(info);
+ ItemInfoWithIcon predictedApp = info.clone();
+ predictedApp.container = LauncherSettings.Favorites.CONTAINER_PREDICTION;
+ predictedApps.add(predictedApp);
} else {
if (FeatureFlags.IS_DOGFOOD_BUILD) {
Log.e(TAG, "Predicted app not found: " + mapper);
@@ -315,8 +321,14 @@
}
}
- public void setTextAlpha(int alpha) {
- mIconCurrentTextAlpha = alpha;
+ public void setTextAlpha(int textAlpha) {
+ mIconLastSetTextAlpha = textAlpha;
+ if (getAlpha() < 1 && textAlpha > 0) {
+ // If the entire header is translucent, make sure the text is at full opacity so it's
+ // not double-translucent. However, we support keeping the text invisible (alpha == 0).
+ textAlpha = mIconFullTextAlpha;
+ }
+ mIconCurrentTextAlpha = textAlpha;
int iconColor = setColorAlphaBound(mIconTextColor, mIconCurrentTextAlpha);
for (int i = 0; i < getChildCount(); i++) {
((BubbleTextView) getChildAt(i)).setTextColor(iconColor);
@@ -324,6 +336,13 @@
}
@Override
+ public void setAlpha(float alpha) {
+ super.setAlpha(alpha);
+ // Reapply text alpha so that we update it to be full alpha if the row is now translucent.
+ setTextAlpha(mIconLastSetTextAlpha);
+ }
+
+ @Override
public boolean hasOverlappingRendering() {
return false;
}
@@ -351,23 +370,15 @@
}
@Override
- public void setContentVisibility(boolean hasHeaderExtra, boolean hasContent,
- PropertySetter setter, Interpolator fadeInterpolator) {
- boolean isDrawn = getAlpha() > 0;
- int textAlpha = hasHeaderExtra
- ? (hasContent ? mIconFullTextAlpha : 0) // Text follows the content visibility
- : mIconCurrentTextAlpha; // Leave as before
- if (!isDrawn) {
- // If the header is not drawn, no need to animate the text alpha
- setTextAlpha(textAlpha);
- } else {
- setter.setInt(this, TEXT_ALPHA, textAlpha, fadeInterpolator);
- }
-
+ public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
+ PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
+ // Text follows all apps visibility
+ int textAlpha = hasHeaderExtra && hasAllAppsContent ? mIconFullTextAlpha : 0;
+ setter.setInt(this, TEXT_ALPHA, textAlpha, allAppsFade);
setter.setFloat(mOverviewScrollFactor, AnimatedFloat.VALUE,
- (hasHeaderExtra && !hasContent) ? 1 : 0, LINEAR);
+ (hasHeaderExtra && !hasAllAppsContent) ? 1 : 0, LINEAR);
setter.setFloat(mContentAlphaFactor, AnimatedFloat.VALUE, hasHeaderExtra ? 1 : 0,
- fadeInterpolator);
+ headerFade);
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
index 085bbc4..9c66107 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
@@ -24,26 +24,33 @@
import android.app.prediction.AppTarget;
import android.content.ComponentName;
import android.content.Context;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+
+import androidx.annotation.NonNull;
import com.android.launcher3.AppInfo;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
+import com.android.launcher3.ItemInfo;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.StateListener;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.MainThreadInitializedObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.stream.IntStream;
/**
* Handler responsible to updating the UI due to predicted apps changes. Operations:
@@ -58,7 +65,7 @@
* 4) Maintains the current active client id (for the predictions) and all updates are performed on
* that client id.
*/
-public class PredictionUiStateManager implements OnGlobalLayoutListener, ItemInfoUpdateReceiver,
+public class PredictionUiStateManager implements StateListener, ItemInfoUpdateReceiver,
OnIDPChangeListener, OnUpdateListener {
public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
@@ -153,7 +160,10 @@
public void reapplyItemInfo(ItemInfoWithIcon info) { }
@Override
- public void onGlobalLayout() {
+ public void onStateTransitionStart(LauncherState toState) { }
+
+ @Override
+ public void onStateTransitionComplete(LauncherState state) {
if (mAppsView == null) {
return;
}
@@ -162,7 +172,8 @@
mPendingState = null;
}
if (mPendingState == null) {
- mAppsView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ Launcher.getLauncher(mAppsView.getContext()).getStateManager()
+ .removeStateListener(this);
}
}
@@ -170,9 +181,8 @@
boolean registerListener = mPendingState == null;
mPendingState = state;
if (registerListener) {
- // OnGlobalLayoutListener is called whenever a view in the view tree changes
- // visibility. Add a listener and wait until appsView is invisible again.
- mAppsView.getViewTreeObserver().addOnGlobalLayoutListener(this);
+ // Add a listener and wait until appsView is invisible again.
+ Launcher.getLauncher(mAppsView.getContext()).getStateManager().addStateListener(this);
}
}
@@ -318,6 +328,30 @@
return mCurrentState;
}
+ /**
+ * Fill in predicted_rank field based on app prediction.
+ * Only applicable when {@link ItemInfo#itemType} is one of the followings:
+ * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
+ * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
+ * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT}
+ */
+ public static void fillInPredictedRank(
+ @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
+ final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate();
+ if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null
+ || (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+ && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
+ && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
+ return;
+ }
+ final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
+ final List<ComponentKeyMapper> predictedApps = manager.getCurrentState().apps;
+ IntStream.range(0, predictedApps.size())
+ .filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
+ .findFirst()
+ .ifPresent((rank) -> target.predictedRank = rank);
+ }
+
public static class PredictionState {
public boolean isEnabled;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index a3c2e3c..cac170c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -28,17 +28,17 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
import com.android.launcher3.util.TouchController;
@@ -145,7 +145,7 @@
ArrayList<TouchController> list = new ArrayList<>();
list.add(launcher.getDragController());
if (mode == NO_BUTTON) {
- list.add(new QuickSwitchTouchController(launcher));
+ list.add(new NoButtonQuickSwitchTouchController(launcher));
list.add(new NavBarToHomeTouchController(launcher));
list.add(new FlingAndHoldTouchController(launcher));
} else {
@@ -164,6 +164,11 @@
}
}
+ if (FeatureFlags.PULL_DOWN_STATUS_BAR
+ && !launcher.getDeviceProfile().isMultiWindowMode) {
+ list.add(new StatusBarTouchController(launcher));
+ }
+
list.add(new LauncherTaskViewController(launcher));
return list.toArray(new TouchController[list.size()]);
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 5ee08c1..63ac528 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -68,8 +68,16 @@
if (taskCount == 0) {
return super.getOverviewScaleAndTranslation(launcher);
}
- TaskView dummyTask = recentsView.getTaskViewAt(Math.max(taskCount - 1,
- recentsView.getCurrentPage()));
+ TaskView dummyTask;
+ if (recentsView.getCurrentPage() >= 0) {
+ if (recentsView.getCurrentPage() <= taskCount - 1) {
+ dummyTask = recentsView.getCurrentPageTaskView();
+ } else {
+ dummyTask = recentsView.getTaskViewAt(taskCount - 1);
+ }
+ } else {
+ dummyTask = recentsView.getTaskViewAt(0);
+ }
return recentsView.getTempClipAnimationHelper().updateForFullscreenOverview(dummyTask)
.getScaleAndTranslation();
}
@@ -90,7 +98,7 @@
if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
// Translate hotseat offscreen if we show it in overview.
ScaleAndTranslation scaleAndTranslation = super.getHotseatScaleAndTranslation(launcher);
- scaleAndTranslation.translationY = LayoutUtils.getShelfTrackingDistance(launcher,
+ scaleAndTranslation.translationY += LayoutUtils.getShelfTrackingDistance(launcher,
launcher.getDeviceProfile());
return scaleAndTranslation;
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
index c954762..427206a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
@@ -15,7 +15,9 @@
*/
package com.android.launcher3.uioverrides.states;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCRIM_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
@@ -43,6 +45,7 @@
if (this == OVERVIEW_PEEK && fromState == NORMAL) {
builder.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
+ builder.setInterpolator(ANIM_OVERVIEW_SCRIM_FADE, FAST_OUT_SLOW_IN);
}
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 5543860..ed5dba1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
@@ -43,10 +44,10 @@
import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -91,8 +92,19 @@
@Override
public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
- // If the hotseat icons are visible in overview, keep them in their normal position.
- return super.getWorkspaceScaleAndTranslation(launcher);
+ DeviceProfile dp = launcher.getDeviceProfile();
+ if (dp.allAppsIconSizePx >= dp.iconSizePx) {
+ return new ScaleAndTranslation(1, 0, 0);
+ } else {
+ float scale = ((float) dp.allAppsIconSizePx) / dp.iconSizePx;
+ // Distance between the screen center (which is the pivotY for hotseat) and the
+ // bottom of the hotseat (which we want to preserve)
+ float distanceFromBottom = dp.heightPx / 2 - dp.hotseatBarBottomPaddingPx;
+ // On scaling, the bottom edge is moved closer to the pivotY. We move the
+ // hotseat back down so that the bottom edge's position is preserved.
+ float translationY = distanceFromBottom * (1 - scale);
+ return new ScaleAndTranslation(scale, 0, translationY);
+ }
}
return getWorkspaceScaleAndTranslation(launcher);
}
@@ -128,14 +140,15 @@
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
} else {
+ boolean hasAllAppsHeaderExtra = launcher.getAppsView() != null
+ && launcher.getAppsView().getFloatingHeaderView().hasVisibleContent();
return HOTSEAT_SEARCH_BOX | VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON |
- (launcher.getAppsView().getFloatingHeaderView().hasVisibleContent()
- ? ALL_APPS_HEADER_EXTRA : HOTSEAT_ICONS);
+ (hasAllAppsHeaderExtra ? ALL_APPS_HEADER_EXTRA : HOTSEAT_ICONS);
}
}
@Override
- public float getWorkspaceScrimAlpha(Launcher launcher) {
+ public float getOverviewScrimAlpha(Launcher launcher) {
return 0.5f;
}
@@ -159,11 +172,7 @@
}
public static float getDefaultSwipeHeight(Launcher launcher) {
- return getDefaultSwipeHeight(launcher.getDeviceProfile());
- }
-
- public static float getDefaultSwipeHeight(DeviceProfile dp) {
- return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
+ return LayoutUtils.getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
}
@Override
@@ -197,6 +206,7 @@
builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
+ builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_7);
builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index 3fe4bcf..3231f37 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -16,34 +16,37 @@
package com.android.launcher3.uioverrides.touchcontrollers;
+import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM;
import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
-import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
+import android.view.View;
import android.view.ViewConfiguration;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppTransitionManagerImpl;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.OverviewInteractionState;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.views.RecentsView;
@@ -72,7 +75,7 @@
@Override
protected long getAtomicDuration() {
- return 300;
+ return LauncherAppTransitionManagerImpl.ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
}
@Override
@@ -100,8 +103,10 @@
}
});
mPeekAnim.start();
- recentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
+
+ mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(isPaused ? 0 : 1,
+ peekDuration, 0);
});
}
}
@@ -120,12 +125,36 @@
LauncherState toState) {
if (fromState == NORMAL && toState == ALL_APPS) {
AnimatorSetBuilder builder = new AnimatorSetBuilder();
+ // Fade in prediction icons quickly, then rest of all apps after reaching overview.
+ float progressToReachOverview = NORMAL.getVerticalProgress(mLauncher)
+ - OVERVIEW.getVerticalProgress(mLauncher);
+ builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(
+ ACCEL,
+ 0,
+ ALL_APPS_CONTENT_FADE_THRESHOLD));
+ builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
+ ACCEL,
+ progressToReachOverview,
+ progressToReachOverview + ALL_APPS_CONTENT_FADE_THRESHOLD));
// Get workspace out of the way quickly, to prepare for potential pause.
builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL_3);
builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, DEACCEL_3);
builder.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_3);
return builder;
+ } else if (fromState == ALL_APPS && toState == NORMAL) {
+ AnimatorSetBuilder builder = new AnimatorSetBuilder();
+ // Keep all apps/predictions opaque until the very end of the transition.
+ float progressToReachOverview = OVERVIEW.getVerticalProgress(mLauncher);
+ builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
+ DEACCEL,
+ progressToReachOverview - ALL_APPS_CONTENT_FADE_THRESHOLD,
+ progressToReachOverview));
+ builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(
+ DEACCEL,
+ 1 - ALL_APPS_CONTENT_FADE_THRESHOLD,
+ 1));
+ return builder;
}
return super.getAnimatorSetBuilderForStates(fromState, toState);
}
@@ -140,20 +169,14 @@
}
@Override
- public void onDragEnd(float velocity, boolean fling) {
+ public void onDragEnd(float velocity) {
if (mMotionPauseDetector.isPaused() && handlingOverviewAnim()) {
if (mPeekAnim != null) {
mPeekAnim.cancel();
}
- AnimatorSetBuilder builder = new AnimatorSetBuilder();
- builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
- if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
- builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
- builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
- }
- AnimatorSet overviewAnim = mLauncher.getStateManager().createAtomicAnimation(
- NORMAL, OVERVIEW, builder, ANIM_ALL, ATOMIC_DURATION);
+ Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
+ INDEX_PAUSE_TO_OVERVIEW_ANIM);
overviewAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -162,12 +185,32 @@
});
overviewAnim.start();
} else {
- super.onDragEnd(velocity, fling);
+ super.onDragEnd(velocity);
+ }
+
+ View searchView = mLauncher.getAppsView().getSearchView();
+ if (searchView instanceof FeedbackHandler) {
+ ((FeedbackHandler) searchView).resetFeedback();
}
mMotionPauseDetector.clear();
}
@Override
+ protected void goToTargetState(LauncherState targetState, int logAction) {
+ if (mPeekAnim != null && mPeekAnim.isStarted()) {
+ // Don't jump to the target state until overview is no longer peeking.
+ mPeekAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ FlingAndHoldTouchController.super.goToTargetState(targetState, logAction);
+ }
+ });
+ } else {
+ super.goToTargetState(targetState, logAction);
+ }
+ }
+
+ @Override
protected void updateAnimatorBuilderOnReinit(AnimatorSetBuilder builder) {
if (handlingOverviewAnim()) {
// We don't want the state transition to all apps to animate overview,
@@ -175,4 +218,16 @@
builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
}
}
+
+ /**
+ * Interface for views with feedback animation requiring reset
+ */
+ public interface FeedbackHandler {
+
+ /**
+ * reset searchWidget feedback
+ */
+ void resetFeedback();
+ }
+
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index d66af1a..ef50c7b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -43,21 +44,25 @@
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.config.BaseFlags;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.TouchController;
+import com.android.quickstep.util.AssistantUtilities;
import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
/**
* Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps.
*/
-public class NavBarToHomeTouchController implements TouchController, SwipeDetector.Listener {
+public class NavBarToHomeTouchController implements TouchController,
+ SingleAxisSwipeDetector.Listener {
private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3;
private final Launcher mLauncher;
- private final SwipeDetector mSwipeDetector;
+ private final SingleAxisSwipeDetector mSwipeDetector;
private final float mPullbackDistance;
private boolean mNoIntercept;
@@ -67,7 +72,8 @@
public NavBarToHomeTouchController(Launcher launcher) {
mLauncher = launcher;
- mSwipeDetector = new SwipeDetector(mLauncher, this, SwipeDetector.VERTICAL);
+ mSwipeDetector = new SingleAxisSwipeDetector(mLauncher, this,
+ SingleAxisSwipeDetector.VERTICAL);
mPullbackDistance = mLauncher.getResources().getDimension(R.dimen.home_pullback_distance);
}
@@ -79,7 +85,8 @@
if (mNoIntercept) {
return false;
}
- mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
+ mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_POSITIVE,
+ false /* ignoreSlop */);
}
if (mNoIntercept) {
@@ -101,6 +108,10 @@
if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
return true;
}
+ if (BaseFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
+ && AssistantUtilities.isExcludedAssistantRunning()) {
+ return true;
+ }
return false;
}
@@ -173,7 +184,8 @@
}
@Override
- public void onDragEnd(float velocity, boolean fling) {
+ public void onDragEnd(float velocity) {
+ boolean fling = mSwipeDetector.isFling(velocity);
final int logAction = fling ? Touch.FLING : Touch.SWIPE;
float progress = mCurrentAnimation.getProgressFraction();
float interpolatedProgress = PULLBACK_INTERPOLATOR.getInterpolation(progress);
@@ -190,6 +202,8 @@
AbstractFloatingView.closeAllOpenViews(mLauncher);
logStateChange(topOpenView.getLogContainerType(), logAction);
}
+ ActivityManagerWrapper.getInstance()
+ .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
} else {
// Quickly return to the state we came from (we didn't move far).
ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
new file mode 100644
index 0000000..76374af
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2019 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.uioverrides.touchcontrollers;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM;
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.QUICK_SWITCH;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_5;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
+import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.CANCEL;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
+import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.QuickstepAppTransitionManagerImpl;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.touch.BaseSwipeDetector;
+import com.android.launcher3.touch.BothAxesSwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.MotionPauseDetector;
+import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
+import com.android.quickstep.util.StaggeredWorkspaceAnim;
+import com.android.quickstep.views.LauncherRecentsView;
+
+/**
+ * Handles quick switching to a recent task from the home screen. To give as much flexibility to
+ * the user as possible, also handles swipe up and hold to go to overview and swiping back home.
+ */
+public class NoButtonQuickSwitchTouchController implements TouchController,
+ BothAxesSwipeDetector.Listener, MotionPauseDetector.OnMotionPauseListener {
+
+ /** The minimum progress of the scale/translationY animation until drag end. */
+ private static final float Y_ANIM_MIN_PROGRESS = 0.15f;
+ private static final Interpolator FADE_OUT_INTERPOLATOR = DEACCEL_5;
+ private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCEL_0_75;
+ private static final Interpolator SCALE_DOWN_INTERPOLATOR = DEACCEL;
+
+ private final Launcher mLauncher;
+ private final BothAxesSwipeDetector mSwipeDetector;
+ private final float mXRange;
+ private final float mYRange;
+ private final MotionPauseDetector mMotionPauseDetector;
+ private final float mMotionPauseMinDisplacement;
+
+ private boolean mNoIntercept;
+ private LauncherState mStartState;
+
+ private ShelfPeekAnim mShelfPeekAnim;
+ private boolean mIsHomeScreenVisible = true;
+
+ // As we drag, we control 3 animations: one to get non-overview components out of the way,
+ // and the other two to set overview properties based on x and y progress.
+ private AnimatorPlaybackController mNonOverviewAnim;
+ private AnimatorPlaybackController mXOverviewAnim;
+ private AnimatorPlaybackController mYOverviewAnim;
+
+ public NoButtonQuickSwitchTouchController(Launcher launcher) {
+ mLauncher = launcher;
+ mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this);
+ mXRange = mLauncher.getDeviceProfile().widthPx / 2f;
+ mYRange = LayoutUtils.getShelfTrackingDistance(mLauncher, mLauncher.getDeviceProfile());
+ mMotionPauseDetector = new MotionPauseDetector(mLauncher);
+ mMotionPauseMinDisplacement = mLauncher.getResources().getDimension(
+ R.dimen.motion_pause_detector_min_displacement_from_app);
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mNoIntercept = !canInterceptTouch(ev);
+ if (mNoIntercept) {
+ return false;
+ }
+
+ // Only detect horizontal swipe for intercept, then we will allow swipe up as well.
+ mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT,
+ false /* ignoreSlopWhenSettling */);
+ }
+
+ if (mNoIntercept) {
+ return false;
+ }
+
+ onControllerTouchEvent(ev);
+ return mSwipeDetector.isDraggingOrSettling();
+ }
+
+ @Override
+ public boolean onControllerTouchEvent(MotionEvent ev) {
+ return mSwipeDetector.onTouchEvent(ev);
+ }
+
+ private boolean canInterceptTouch(MotionEvent ev) {
+ if (!mLauncher.isInState(LauncherState.NORMAL)) {
+ return false;
+ }
+ if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) {
+ return false;
+ }
+ int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags();
+ if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void onDragStart(boolean start) {
+ mMotionPauseDetector.clear();
+ if (start) {
+ mShelfPeekAnim = ((QuickstepAppTransitionManagerImpl) mLauncher
+ .getAppTransitionManager()).getShelfPeekAnim();
+
+ mStartState = mLauncher.getStateManager().getState();
+
+ mMotionPauseDetector.setOnMotionPauseListener(this);
+
+ // We have detected horizontal drag start, now allow swipe up as well.
+ mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT | DIRECTION_UP,
+ false /* ignoreSlopWhenSettling */);
+
+ setupAnimators();
+ }
+ }
+
+ @Override
+ public void onMotionPauseChanged(boolean isPaused) {
+ ShelfAnimState shelfState = isPaused ? PEEK : HIDE;
+ if (shelfState == PEEK) {
+ // Some shelf elements (e.g. qsb) were hidden, but we need them visible when peeking.
+ AnimatorSetBuilder builder = new AnimatorSetBuilder();
+ AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
+ allAppsController.setAlphas(NORMAL.getVisibleElements(mLauncher),
+ new AnimationConfig(), builder);
+ builder.build().setDuration(0).start();
+
+ if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+ // Hotseat was hidden, but we need it visible when peeking.
+ mLauncher.getHotseat().setAlpha(1);
+ }
+ }
+ mShelfPeekAnim.setShelfState(shelfState, ShelfPeekAnim.INTERPOLATOR,
+ ShelfPeekAnim.DURATION);
+ VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
+ }
+
+ private void setupAnimators() {
+ // Animate the non-overview components (e.g. workspace, shelf) out of the way.
+ AnimatorSetBuilder nonOverviewBuilder = new AnimatorSetBuilder();
+ nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_FADE, FADE_OUT_INTERPOLATOR);
+ nonOverviewBuilder.setInterpolator(ANIM_ALL_APPS_FADE, FADE_OUT_INTERPOLATOR);
+ nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, TRANSLATE_OUT_INTERPOLATOR);
+ nonOverviewBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, TRANSLATE_OUT_INTERPOLATOR);
+ updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder, ANIM_ALL);
+ mNonOverviewAnim.dispatchOnStart();
+
+ setupOverviewAnimators();
+ }
+
+ /** Create state animation to control non-overview components. */
+ private void updateNonOverviewAnim(LauncherState toState, AnimatorSetBuilder builder,
+ @LauncherStateManager.AnimationComponents int animComponents) {
+ builder.addFlag(FLAG_DONT_ANIMATE_OVERVIEW);
+ long accuracy = (long) (Math.max(mXRange, mYRange) * 2);
+ mNonOverviewAnim = mLauncher.getStateManager().createAnimationToNewWorkspace(toState,
+ builder, accuracy, this::clearState, animComponents);
+ }
+
+ private void setupOverviewAnimators() {
+ final LauncherState fromState = QUICK_SWITCH;
+ final LauncherState toState = OVERVIEW;
+ LauncherState.ScaleAndTranslation fromScaleAndTranslation = fromState
+ .getOverviewScaleAndTranslation(mLauncher);
+ LauncherState.ScaleAndTranslation toScaleAndTranslation = toState
+ .getOverviewScaleAndTranslation(mLauncher);
+ // Update RecentView's translationX to have it start offscreen.
+ LauncherRecentsView recentsView = mLauncher.getOverviewPanel();
+ float startScale = Utilities.mapRange(
+ SCALE_DOWN_INTERPOLATOR.getInterpolation(Y_ANIM_MIN_PROGRESS),
+ fromScaleAndTranslation.scale,
+ toScaleAndTranslation.scale);
+ fromScaleAndTranslation.translationX = recentsView.getOffscreenTranslationX(startScale);
+
+ // Set RecentView's initial properties.
+ recentsView.setScaleX(fromScaleAndTranslation.scale);
+ recentsView.setScaleY(fromScaleAndTranslation.scale);
+ recentsView.setTranslationX(fromScaleAndTranslation.translationX);
+ recentsView.setTranslationY(fromScaleAndTranslation.translationY);
+ recentsView.setContentAlpha(1);
+
+ // As we drag right, animate the following properties:
+ // - RecentsView translationX
+ // - OverviewScrim
+ AnimatorSet xOverviewAnim = new AnimatorSet();
+ xOverviewAnim.play(ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_X,
+ toScaleAndTranslation.translationX));
+ xOverviewAnim.play(ObjectAnimator.ofFloat(
+ mLauncher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS,
+ toState.getOverviewScrimAlpha(mLauncher)));
+ long xAccuracy = (long) (mXRange * 2);
+ xOverviewAnim.setDuration(xAccuracy);
+ mXOverviewAnim = AnimatorPlaybackController.wrap(xOverviewAnim, xAccuracy);
+ mXOverviewAnim.dispatchOnStart();
+
+ // As we drag up, animate the following properties:
+ // - RecentsView translationY
+ // - RecentsView scale
+ // - RecentsView fullscreenProgress
+ AnimatorSet yAnimation = new AnimatorSet();
+ Animator translateYAnim = ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_Y,
+ toScaleAndTranslation.translationY);
+ Animator scaleAnim = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY,
+ toScaleAndTranslation.scale);
+ Animator fullscreenProgressAnim = ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS,
+ fromState.getOverviewFullscreenProgress(), toState.getOverviewFullscreenProgress());
+ scaleAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR);
+ fullscreenProgressAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR);
+ yAnimation.play(translateYAnim);
+ yAnimation.play(scaleAnim);
+ yAnimation.play(fullscreenProgressAnim);
+ long yAccuracy = (long) (mYRange * 2);
+ yAnimation.setDuration(yAccuracy);
+ mYOverviewAnim = AnimatorPlaybackController.wrap(yAnimation, yAccuracy);
+ mYOverviewAnim.dispatchOnStart();
+ }
+
+ @Override
+ public boolean onDrag(PointF displacement, MotionEvent ev) {
+ float xProgress = Math.max(0, displacement.x) / mXRange;
+ float yProgress = Math.max(0, -displacement.y) / mYRange;
+ yProgress = Utilities.mapRange(yProgress, Y_ANIM_MIN_PROGRESS, 1f);
+
+ boolean wasHomeScreenVisible = mIsHomeScreenVisible;
+ if (wasHomeScreenVisible && mNonOverviewAnim != null) {
+ mNonOverviewAnim.setPlayFraction(xProgress);
+ }
+ mIsHomeScreenVisible = FADE_OUT_INTERPOLATOR.getInterpolation(xProgress)
+ <= 1 - ALPHA_CUTOFF_THRESHOLD;
+
+ if (wasHomeScreenVisible && !mIsHomeScreenVisible) {
+ // Get the shelf all the way offscreen so it pops up when we decide to peek it.
+ mShelfPeekAnim.setShelfState(HIDE, LINEAR, 0);
+ }
+
+ // Only allow motion pause if the home screen is invisible, since some
+ // home screen elements will appear in the shelf on motion pause.
+ mMotionPauseDetector.setDisallowPause(mIsHomeScreenVisible
+ || -displacement.y < mMotionPauseMinDisplacement);
+ mMotionPauseDetector.addPosition(displacement.y, ev.getEventTime());
+
+ if (mIsHomeScreenVisible) {
+ // Cancel the shelf anim so it doesn't clobber mNonOverviewAnim.
+ mShelfPeekAnim.setShelfState(CANCEL, LINEAR, 0);
+ }
+
+ if (mXOverviewAnim != null) {
+ mXOverviewAnim.setPlayFraction(xProgress);
+ }
+ if (mYOverviewAnim != null) {
+ mYOverviewAnim.setPlayFraction(yProgress);
+ }
+ return true;
+ }
+
+ @Override
+ public void onDragEnd(PointF velocity) {
+ boolean horizontalFling = mSwipeDetector.isFling(velocity.x);
+ boolean verticalFling = mSwipeDetector.isFling(velocity.y);
+ boolean noFling = !horizontalFling && !verticalFling;
+ int logAction = noFling ? Touch.SWIPE : Touch.FLING;
+ if (mMotionPauseDetector.isPaused() && noFling) {
+ cancelAnimations();
+
+ Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
+ INDEX_PAUSE_TO_OVERVIEW_ANIM);
+ overviewAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onAnimationToStateCompleted(OVERVIEW, logAction);
+ }
+ });
+ overviewAnim.start();
+ return;
+ }
+
+ final LauncherState targetState;
+ if (horizontalFling && verticalFling) {
+ if (velocity.x < 0) {
+ // Flinging left and up or down both go back home.
+ targetState = NORMAL;
+ } else {
+ if (velocity.y > 0) {
+ // Flinging right and down goes to quick switch.
+ targetState = QUICK_SWITCH;
+ } else {
+ // Flinging up and right could go either home or to quick switch.
+ // Determine the target based on the higher velocity.
+ targetState = Math.abs(velocity.x) > Math.abs(velocity.y)
+ ? QUICK_SWITCH : NORMAL;
+ }
+ }
+ } else if (horizontalFling) {
+ targetState = velocity.x > 0 ? QUICK_SWITCH : NORMAL;
+ } else if (verticalFling) {
+ targetState = velocity.y > 0 ? QUICK_SWITCH : NORMAL;
+ } else {
+ // If user isn't flinging, just snap to the closest state based on x progress.
+ boolean passedHorizontalThreshold = mXOverviewAnim.getInterpolatedProgress() > 0.5f;
+ targetState = passedHorizontalThreshold ? QUICK_SWITCH : NORMAL;
+ }
+
+ // Animate the various components to the target state.
+
+ float xProgress = mXOverviewAnim.getProgressFraction();
+ float startXProgress = Utilities.boundToRange(xProgress
+ + velocity.x * getSingleFrameMs(mLauncher) / mXRange, 0f, 1f);
+ final float endXProgress = targetState == NORMAL ? 0 : 1;
+ long xDuration = BaseSwipeDetector.calculateDuration(velocity.x,
+ Math.abs(endXProgress - startXProgress));
+ ValueAnimator xOverviewAnim = mXOverviewAnim.getAnimationPlayer();
+ xOverviewAnim.setFloatValues(startXProgress, endXProgress);
+ xOverviewAnim.setDuration(xDuration)
+ .setInterpolator(scrollInterpolatorForVelocity(velocity.x));
+ mXOverviewAnim.dispatchOnStartWithVelocity(endXProgress, velocity.x);
+
+ boolean flingUpToNormal = verticalFling && velocity.y < 0 && targetState == NORMAL;
+
+ float yProgress = mYOverviewAnim.getProgressFraction();
+ float startYProgress = Utilities.boundToRange(yProgress
+ - velocity.y * getSingleFrameMs(mLauncher) / mYRange, 0f, 1f);
+ final float endYProgress;
+ if (flingUpToNormal) {
+ endYProgress = 1;
+ } else if (targetState == NORMAL) {
+ // Keep overview at its current scale/translationY as it slides off the screen.
+ endYProgress = startYProgress;
+ } else {
+ endYProgress = 0;
+ }
+ long yDuration = BaseSwipeDetector.calculateDuration(velocity.y,
+ Math.abs(endYProgress - startYProgress));
+ ValueAnimator yOverviewAnim = mYOverviewAnim.getAnimationPlayer();
+ yOverviewAnim.setFloatValues(startYProgress, endYProgress);
+ yOverviewAnim.setDuration(yDuration);
+ mYOverviewAnim.dispatchOnStartWithVelocity(endYProgress, velocity.y);
+
+ ValueAnimator nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
+ if (flingUpToNormal && !mIsHomeScreenVisible) {
+ // We are flinging to home while workspace is invisible, run the same staggered
+ // animation as from an app.
+ // Update mNonOverviewAnim to do nothing so it doesn't interfere.
+ updateNonOverviewAnim(targetState, new AnimatorSetBuilder(), 0 /* animComponents */);
+ nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
+
+ new StaggeredWorkspaceAnim(mLauncher, velocity.y, false /* animateOverviewScrim */)
+ .start();
+ } else {
+ boolean canceled = targetState == NORMAL;
+ if (canceled) {
+ // Let the state manager know that the animation didn't go to the target state,
+ // but don't clean up yet (we already clean up when the animation completes).
+ mNonOverviewAnim.dispatchOnCancelWithoutCancelRunnable();
+ }
+ float startProgress = mNonOverviewAnim.getProgressFraction();
+ float endProgress = canceled ? 0 : 1;
+ nonOverviewAnim.setFloatValues(startProgress, endProgress);
+ mNonOverviewAnim.dispatchOnStartWithVelocity(endProgress,
+ horizontalFling ? velocity.x : velocity.y);
+ }
+
+ nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
+ mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState, logAction));
+
+ cancelAnimations();
+ xOverviewAnim.start();
+ yOverviewAnim.start();
+ nonOverviewAnim.start();
+ }
+
+ private void onAnimationToStateCompleted(LauncherState targetState, int logAction) {
+ mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
+ getDirectionForLog(), mSwipeDetector.getDownX(), mSwipeDetector.getDownY(),
+ LauncherLogProto.ContainerType.NAVBAR,
+ mStartState.containerType,
+ targetState.containerType,
+ mLauncher.getWorkspace().getCurrentPage());
+ mLauncher.getStateManager().goToState(targetState, false, this::clearState);
+ }
+
+ private int getDirectionForLog() {
+ return Utilities.isRtl(mLauncher.getResources()) ? Direction.LEFT : Direction.RIGHT;
+ }
+
+ private void cancelAnimations() {
+ if (mNonOverviewAnim != null) {
+ mNonOverviewAnim.getAnimationPlayer().cancel();
+ }
+ if (mXOverviewAnim != null) {
+ mXOverviewAnim.getAnimationPlayer().cancel();
+ }
+ if (mYOverviewAnim != null) {
+ mYOverviewAnim.getAnimationPlayer().cancel();
+ }
+ mShelfPeekAnim.setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
+ mMotionPauseDetector.clear();
+ }
+
+ private void clearState() {
+ cancelAnimations();
+ mNonOverviewAnim = null;
+ mXOverviewAnim = null;
+ mYOverviewAnim = null;
+ mIsHomeScreenVisible = true;
+ mSwipeDetector.finishedScrolling();
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
index 73f328b..9091168 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
@@ -75,7 +75,7 @@
}
@Override
- protected int getLogContainerTypeForNormalState() {
+ protected int getLogContainerTypeForNormalState(MotionEvent ev) {
return LauncherLogProto.ContainerType.WORKSPACE;
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
index 20a2487..03862db 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
@@ -47,9 +47,9 @@
* @return true if we should intercept the motion event
*/
boolean canInterceptTouch(MotionEvent ev) {
- if (mRecentsView.getChildCount() > 0) {
+ if (mRecentsView.getTaskViewCount() > 0) {
// Allow swiping up in the gap between the hotseat and overview.
- return ev.getY() >= mRecentsView.getChildAt(0).getBottom();
+ return ev.getY() >= mRecentsView.getTaskViewAt(0).getBottom();
} else {
// If there are no tasks, we only intercept if we're below the hotseat height.
return isTouchOverHotseat(mLauncher, ev);
@@ -63,7 +63,7 @@
* @return true if going back should take the user to the currently running task
*/
boolean shouldSwipeDownReturnToApp() {
- TaskView taskView = mRecentsView.getTaskViewAt(mRecentsView.getNextPage());
+ TaskView taskView = mRecentsView.getNextPageTaskView();
return taskView != null && mRecentsView.shouldSwipeDownLaunchApp();
}
@@ -76,7 +76,7 @@
*/
PendingAnimation createSwipeDownToTaskAppAnimation(long duration) {
mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen());
- TaskView taskView = mRecentsView.getTaskViewAt(mRecentsView.getCurrentPage());
+ TaskView taskView = mRecentsView.getCurrentPageTaskView();
if (taskView == null) {
throw new IllegalStateException("There is no task view to animate to.");
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 18b8af4..14216ff 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -30,6 +30,7 @@
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.view.MotionEvent;
@@ -42,7 +43,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.quickstep.OverviewInteractionState;
@@ -50,7 +51,7 @@
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
/**
* Handles quick switching to a recent task from the home screen.
@@ -60,10 +61,10 @@
private @Nullable TaskView mTaskToLaunch;
public QuickSwitchTouchController(Launcher launcher) {
- this(launcher, SwipeDetector.HORIZONTAL);
+ this(launcher, SingleAxisSwipeDetector.HORIZONTAL);
}
- protected QuickSwitchTouchController(Launcher l, SwipeDetector.Direction dir) {
+ protected QuickSwitchTouchController(Launcher l, SingleAxisSwipeDetector.Direction dir) {
super(l, dir);
}
@@ -95,6 +96,8 @@
super.onDragStart(start);
mStartContainerType = LauncherLogProto.ContainerType.NAVBAR;
mTaskToLaunch = mLauncher.<RecentsView>getOverviewPanel().getTaskViewAt(0);
+ ActivityManagerWrapper.getInstance()
+ .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
}
@Override
@@ -154,7 +157,7 @@
}
@Override
- protected int getLogContainerTypeForNormalState() {
+ protected int getLogContainerTypeForNormalState(MotionEvent ev) {
return LauncherLogProto.ContainerType.NAVBAR;
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 8e32bb3..ad02de1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -16,10 +16,13 @@
package com.android.launcher3.uioverrides.touchcontrollers;
import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -32,7 +35,8 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.BaseSwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.FlingBlockCheck;
import com.android.launcher3.util.PendingAnimation;
@@ -46,15 +50,14 @@
* Touch controller for handling task view card swipes
*/
public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
- extends AnimatorListenerAdapter implements TouchController, SwipeDetector.Listener {
-
- private static final String TAG = "OverviewSwipeController";
+ extends AnimatorListenerAdapter implements TouchController,
+ SingleAxisSwipeDetector.Listener {
// Progress after which the transition is assumed to be a success in case user does not fling
public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
protected final T mActivity;
- private final SwipeDetector mDetector;
+ private final SingleAxisSwipeDetector mDetector;
private final RecentsView mRecentsView;
private final int[] mTempCords = new int[2];
@@ -74,7 +77,7 @@
public TaskViewTouchController(T activity) {
mActivity = activity;
mRecentsView = activity.getOverviewPanel();
- mDetector = new SwipeDetector(activity, this, SwipeDetector.VERTICAL);
+ mDetector = new SingleAxisSwipeDetector(activity, this, SingleAxisSwipeDetector.VERTICAL);
}
private boolean canInterceptTouch() {
@@ -113,7 +116,7 @@
int directionsToDetectScroll = 0;
boolean ignoreSlopWhenSettling = false;
if (mCurrentAnimation != null) {
- directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+ directionsToDetectScroll = DIRECTION_BOTH;
ignoreSlopWhenSettling = true;
} else {
mTaskBeingDragged = null;
@@ -126,12 +129,12 @@
if (!SysUINavigationMode.getMode(mActivity).hasGestures) {
// Don't allow swipe down to open if we don't support swipe up
// to enter overview.
- directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+ directionsToDetectScroll = DIRECTION_POSITIVE;
} else {
// The task can be dragged up to dismiss it,
// and down to open if it's the current page.
directionsToDetectScroll = i == mRecentsView.getCurrentPage()
- ? SwipeDetector.DIRECTION_BOTH : SwipeDetector.DIRECTION_POSITIVE;
+ ? DIRECTION_BOTH : DIRECTION_POSITIVE;
}
break;
}
@@ -165,8 +168,8 @@
return;
}
int scrollDirections = mDetector.getScrollDirections();
- if (goingUp && ((scrollDirections & SwipeDetector.DIRECTION_POSITIVE) == 0)
- || !goingUp && ((scrollDirections & SwipeDetector.DIRECTION_NEGATIVE) == 0)) {
+ if (goingUp && ((scrollDirections & DIRECTION_POSITIVE) == 0)
+ || !goingUp && ((scrollDirections & DIRECTION_NEGATIVE) == 0)) {
// Trying to re-init in an unsupported direction.
return;
}
@@ -243,7 +246,8 @@
}
@Override
- public void onDragEnd(float velocity, boolean fling) {
+ public void onDragEnd(float velocity) {
+ boolean fling = mDetector.isFling(velocity);
final boolean goingToEnd;
final int logAction;
boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
@@ -260,14 +264,14 @@
logAction = Touch.SWIPE;
goingToEnd = interpolatedProgress > SUCCESS_TRANSITION_PROGRESS;
}
- long animationDuration = SwipeDetector.calculateDuration(
+ long animationDuration = BaseSwipeDetector.calculateDuration(
velocity, goingToEnd ? (1 - progress) : progress);
if (blockedFling && !goingToEnd) {
animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity);
}
- float nextFrameProgress = Utilities.boundToRange(
- progress + velocity * SINGLE_FRAME_MS / Math.abs(mEndDisplacement), 0f, 1f);
+ float nextFrameProgress = Utilities.boundToRange(progress
+ + velocity * getSingleFrameMs(mActivity) / Math.abs(mEndDisplacement), 0f, 1f);
mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd, logAction));
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
index f1e4041..0ed5291 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
@@ -17,12 +17,12 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
public class TransposedQuickSwitchTouchController extends QuickSwitchTouchController {
public TransposedQuickSwitchTouchController(Launcher launcher) {
- super(launcher, SwipeDetector.VERTICAL);
+ super(launcher, SingleAxisSwipeDetector.VERTICAL);
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index 5e77e0a..ad90e16 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -81,6 +81,7 @@
});
factory.onRemoteAnimationReceived(null);
factory.createActivityController(RECENTS_LAUNCH_DURATION);
+ factory.setRecentsAttachedToAppWindow(true, false);
mActivity = activity;
mRecentsView = mActivity.getOverviewPanel();
return false;
@@ -101,6 +102,7 @@
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
+ mHelper.onSwipeUpToRecentsComplete(mActivity);
if (mRecentsView != null) {
mRecentsView.animateUpRunningTaskIconScale();
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
new file mode 100644
index 0000000..e5d2b41
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2019 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.quickstep;
+
+import static com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
+import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
+
+import android.animation.Animator;
+import android.annotation.TargetApi;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.util.VibratorWrapper;
+import com.android.launcher3.views.FloatingIconView;
+import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
+import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
+import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.inputconsumers.InputConsumer;
+import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
+import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.util.SwipeAnimationTargetSet;
+import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
+
+import java.util.function.Consumer;
+
+/**
+ * Base class for swipe up handler with some utility methods
+ */
+@TargetApi(Build.VERSION_CODES.Q)
+public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q extends RecentsView>
+ implements SwipeAnimationListener {
+
+ private static final String TAG = "BaseSwipeUpHandler";
+ protected static final Rect TEMP_RECT = new Rect();
+
+ // Start resisting when swiping past this factor of mTransitionDragLength.
+ private static final float DRAG_LENGTH_FACTOR_START_PULLBACK = 1.4f;
+ // This is how far down we can scale down, where 0f is full screen and 1f is recents.
+ private static final float DRAG_LENGTH_FACTOR_MAX_PULLBACK = 1.8f;
+ private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
+
+ // The distance needed to drag to reach the task size in recents.
+ protected int mTransitionDragLength;
+ // How much further we can drag past recents, as a factor of mTransitionDragLength.
+ protected float mDragLengthFactor = 1;
+
+ protected final Context mContext;
+ protected final OverviewComponentObserver mOverviewComponentObserver;
+ protected final ActivityControlHelper<T> mActivityControlHelper;
+ protected final RecentsModel mRecentsModel;
+ protected final int mRunningTaskId;
+
+ protected final ClipAnimationHelper mClipAnimationHelper;
+ protected final TransformParams mTransformParams = new TransformParams();
+
+ protected final Mode mMode;
+
+ // Shift in the range of [0, 1].
+ // 0 => preview snapShot is completely visible, and hotseat is completely translated down
+ // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
+ // visible.
+ protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
+
+ protected final ActivityInitListener mActivityInitListener;
+ protected final RecentsAnimationWrapper mRecentsAnimationWrapper;
+
+ protected T mActivity;
+ protected Q mRecentsView;
+ protected DeviceProfile mDp;
+ private final int mPageSpacing;
+
+ protected Runnable mGestureEndCallback;
+
+ protected final Handler mMainThreadHandler = MAIN_EXECUTOR.getHandler();
+ protected MultiStateCallback mStateCallback;
+
+ protected boolean mCanceled;
+ protected int mFinishingRecentsAnimationForNewTaskId = -1;
+
+ protected BaseSwipeUpHandler(Context context,
+ OverviewComponentObserver overviewComponentObserver,
+ RecentsModel recentsModel, InputConsumerController inputConsumer, int runningTaskId) {
+ mContext = context;
+ mOverviewComponentObserver = overviewComponentObserver;
+ mActivityControlHelper = overviewComponentObserver.getActivityControlHelper();
+ mRecentsModel = recentsModel;
+ mActivityInitListener =
+ mActivityControlHelper.createActivityInitListener(this::onActivityInit);
+ mRunningTaskId = runningTaskId;
+ mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
+ this::createNewInputProxyHandler);
+ mMode = SysUINavigationMode.getMode(context);
+
+ mClipAnimationHelper = new ClipAnimationHelper(context);
+ mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
+ initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext)
+ .getDeviceProfile(mContext));
+ }
+
+ protected void setStateOnUiThread(int stateFlag) {
+ if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
+ mStateCallback.setState(stateFlag);
+ } else {
+ postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag));
+ }
+ }
+
+ protected void performHapticFeedback() {
+ VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+ }
+
+ public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode rotationMode) {
+ return mRecentsView != null ? mRecentsView.getEventDispatcher(rotationMode) : null;
+ }
+
+ @UiThread
+ public void updateDisplacement(float displacement) {
+ // We are moving in the negative x/y direction
+ displacement = -displacement;
+ float shift;
+ if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) {
+ shift = mDragLengthFactor;
+ } else {
+ float translation = Math.max(displacement, 0);
+ shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
+ if (shift > DRAG_LENGTH_FACTOR_START_PULLBACK) {
+ float pullbackProgress = Utilities.getProgress(shift,
+ DRAG_LENGTH_FACTOR_START_PULLBACK, mDragLengthFactor);
+ pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
+ shift = DRAG_LENGTH_FACTOR_START_PULLBACK + pullbackProgress
+ * (DRAG_LENGTH_FACTOR_MAX_PULLBACK - DRAG_LENGTH_FACTOR_START_PULLBACK);
+ }
+ }
+
+ mCurrentShift.updateValue(shift);
+ }
+
+ public void setGestureEndCallback(Runnable gestureEndCallback) {
+ mGestureEndCallback = gestureEndCallback;
+ }
+
+ public abstract Intent getLaunchIntent();
+
+ protected void linkRecentsViewScroll() {
+ SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> {
+ mTransformParams.setSyncTransactionApplier(applier);
+ mRecentsAnimationWrapper.runOnInit(() ->
+ mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier));
+ });
+
+ mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+ if (moveWindowWithRecentsScroll()) {
+ updateFinalShift();
+ }
+ });
+ mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
+ mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
+ }
+
+ protected void startNewTask(int successStateFlag, Consumer<Boolean> resultCallback) {
+ // Launch the task user scrolled to (mRecentsView.getNextPage()).
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ // We finish recents animation inside launchTask() when live tile is enabled.
+ mRecentsView.getNextPageTaskView().launchTask(false /* animate */,
+ true /* freezeTaskList */);
+ } else {
+ int taskId = mRecentsView.getNextPageTaskView().getTask().key.id;
+ mFinishingRecentsAnimationForNewTaskId = taskId;
+ mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
+ if (!mCanceled) {
+ TaskView nextTask = mRecentsView.getTaskView(taskId);
+ if (nextTask != null) {
+ nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
+ success -> {
+ resultCallback.accept(success);
+ if (!success) {
+ mActivityControlHelper.onLaunchTaskFailed(mActivity);
+ nextTask.notifyTaskLaunchFailed(TAG);
+ } else {
+ mActivityControlHelper.onLaunchTaskSuccess(mActivity);
+ }
+ }, mMainThreadHandler);
+ }
+ setStateOnUiThread(successStateFlag);
+ }
+ mCanceled = false;
+ mFinishingRecentsAnimationForNewTaskId = -1;
+ });
+ }
+ TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
+ }
+
+ @Override
+ public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
+ DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
+ final Rect overviewStackBounds;
+ RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
+
+ if (targetSet.minimizedHomeBounds != null && runningTaskTarget != null) {
+ overviewStackBounds = mActivityControlHelper
+ .getOverviewWindowBounds(targetSet.minimizedHomeBounds, runningTaskTarget);
+ dp = dp.getMultiWindowProfile(mContext, new Point(
+ overviewStackBounds.width(), overviewStackBounds.height()));
+ } else {
+ // If we are not in multi-window mode, home insets should be same as system insets.
+ dp = dp.copy(mContext);
+ overviewStackBounds = getStackBounds(dp);
+ }
+ dp.updateInsets(targetSet.homeContentInsets);
+ dp.updateIsSeascape(mContext);
+ if (runningTaskTarget != null) {
+ mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
+ }
+
+ mClipAnimationHelper.prepareAnimation(dp, false /* isOpening */);
+ initTransitionEndpoints(dp);
+
+ mRecentsAnimationWrapper.setController(targetSet);
+ }
+
+ private Rect getStackBounds(DeviceProfile dp) {
+ if (mActivity != null) {
+ int loc[] = new int[2];
+ View rootView = mActivity.getRootView();
+ rootView.getLocationOnScreen(loc);
+ return new Rect(loc[0], loc[1], loc[0] + rootView.getWidth(),
+ loc[1] + rootView.getHeight());
+ } else {
+ return new Rect(0, 0, dp.widthPx, dp.heightPx);
+ }
+ }
+
+ protected void initTransitionEndpoints(DeviceProfile dp) {
+ mDp = dp;
+
+ mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
+ dp, mContext, TEMP_RECT);
+ if (!dp.isMultiWindowMode) {
+ // When updating the target rect, also update the home bounds since the location on
+ // screen of the launcher window may be stale (position is not updated until first
+ // traversal after the window is resized). We only do this for non-multiwindow because
+ // we otherwise use the minimized home bounds provided by the system.
+ mClipAnimationHelper.updateHomeBounds(getStackBounds(dp));
+ }
+ mClipAnimationHelper.updateTargetRect(TEMP_RECT);
+ if (mMode == Mode.NO_BUTTON) {
+ // We can drag all the way to the top of the screen.
+ mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
+ }
+ }
+
+ /**
+ * Return true if the window should be translated horizontally if the recents view scrolls
+ */
+ protected abstract boolean moveWindowWithRecentsScroll();
+
+ protected abstract boolean onActivityInit(final T activity, Boolean alreadyOnHome);
+
+ /**
+ * Called to create a input proxy for the running task
+ */
+ @UiThread
+ protected abstract InputConsumer createNewInputProxyHandler();
+
+ /**
+ * Called when the value of {@link #mCurrentShift} changes
+ */
+ @UiThread
+ public abstract void updateFinalShift();
+
+ /**
+ * Called when motion pause is detected
+ */
+ public abstract void onMotionPauseChanged(boolean isPaused);
+
+ @UiThread
+ public void onGestureStarted() { }
+
+ @UiThread
+ public abstract void onGestureCancelled();
+
+ @UiThread
+ public abstract void onGestureEnded(float endVelocity, PointF velocity, PointF downPos);
+
+ public abstract void onConsumerAboutToBeSwitched(SwipeSharedState sharedState);
+
+ public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { }
+
+ public void initWhenReady() {
+ // Preload the plan
+ mRecentsModel.getTasks(null);
+
+ mActivityInitListener.register();
+ }
+
+ /**
+ * Applies the transform on the recents animation without any additional null checks
+ */
+ protected void applyTransformUnchecked() {
+ float shift = mCurrentShift.value;
+ float offsetX = mRecentsView == null ? 0 : mRecentsView.getScrollOffset();
+ float offsetScale = getTaskCurveScaleForOffsetX(offsetX,
+ mClipAnimationHelper.getTargetRect().width());
+ mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale);
+ mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
+ mTransformParams);
+ }
+
+ private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) {
+ float distanceToReachEdge = mDp.widthPx / 2 + taskWidth / 2 + mPageSpacing;
+ float interpolation = Math.min(1, offsetX / distanceToReachEdge);
+ return TaskView.getCurveScaleForInterpolation(interpolation);
+ }
+
+ /**
+ * Creates an animation that transforms the current app window into the home app.
+ * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
+ * @param homeAnimationFactory The home animation factory.
+ */
+ protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
+ HomeAnimationFactory homeAnimationFactory) {
+ final RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet;
+ final RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet,
+ mTransformParams.setProgress(startProgress), false /* launcherOnTop */));
+ final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
+
+ final View floatingView = homeAnimationFactory.getFloatingView();
+ final boolean isFloatingIconView = floatingView instanceof FloatingIconView;
+ RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext.getResources());
+ if (isFloatingIconView) {
+ FloatingIconView fiv = (FloatingIconView) floatingView;
+ anim.addAnimatorListener(fiv);
+ fiv.setOnTargetChangeListener(anim::onTargetPositionChanged);
+ }
+
+ AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome();
+
+ // End on a "round-enough" radius so that the shape reveal doesn't have to do too much
+ // rounding at the end of the animation.
+ float startRadius = mClipAnimationHelper.getCurrentCornerRadius();
+ float endRadius = startRect.width() / 6f;
+
+ float startTransformProgress = mTransformParams.getProgress();
+ float endTransformProgress = 1;
+
+ // We want the window alpha to be 0 once this threshold is met, so that the
+ // FolderIconView can be seen morphing into the icon shape.
+ final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f;
+ anim.addOnUpdateListener(new RectFSpringAnim.OnUpdateListener() {
+
+ // Alpha interpolates between [1, 0] between progress values [start, end]
+ final float start = 0f;
+ final float end = 0.85f;
+
+ private float getWindowAlpha(float progress) {
+ if (progress <= start) {
+ return 1f;
+ }
+ if (progress >= end) {
+ return 0f;
+ }
+ return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
+ }
+
+ @Override
+ public void onUpdate(RectF currentRect, float progress) {
+ homeAnim.setPlayFraction(progress);
+
+ mTransformParams.setProgress(
+ Utilities.mapRange(progress, startTransformProgress, endTransformProgress))
+ .setCurrentRectAndTargetAlpha(currentRect, getWindowAlpha(progress));
+ if (isFloatingIconView) {
+ mTransformParams.setCornerRadius(endRadius * progress + startRadius
+ * (1f - progress));
+ }
+ mClipAnimationHelper.applyTransform(targetSet, mTransformParams,
+ false /* launcherOnTop */);
+
+ if (isFloatingIconView) {
+ ((FloatingIconView) floatingView).update(currentRect, 1f, progress,
+ windowAlphaThreshold, mClipAnimationHelper.getCurrentCornerRadius(),
+ false);
+ }
+ }
+
+ @Override
+ public void onCancel() {
+ if (isFloatingIconView) {
+ ((FloatingIconView) floatingView).fastFinish();
+ }
+ }
+ });
+ anim.addAnimatorListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ homeAnim.dispatchOnStart();
+ }
+
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ homeAnim.getAnimationPlayer().end();
+ }
+ });
+ return anim;
+ }
+
+ public interface Factory {
+
+ BaseSwipeUpHandler newHandler(RunningTaskInfo runningTask,
+ long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask);
+ }
+
+ protected interface RunningWindowAnim {
+ void end();
+
+ void cancel();
+
+ static RunningWindowAnim wrap(Animator animator) {
+ return new RunningWindowAnim() {
+ @Override
+ public void end() {
+ animator.end();
+ }
+
+ @Override
+ public void cancel() {
+ animator.cancel();
+ }
+ };
+ }
+
+ static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) {
+ return new RunningWindowAnim() {
+ @Override
+ public void end() {
+ rectFSpringAnim.end();
+ }
+
+ @Override
+ public void cancel() {
+ rectFSpringAnim.cancel();
+ }
+ };
+ }
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
index c43155b..8c5a788 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -80,7 +80,9 @@
@Override
public void onAssistantVisibilityChanged(float visibility) {
- // TODO:
+ // This class becomes active when the screen is locked.
+ // Rather than having it handle assistant visibility changes, the assistant visibility is
+ // set to zero prior to this class becoming active.
}
@NonNull
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index b2a71a4..54a366d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -24,7 +24,6 @@
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.INSTANT;
@@ -40,7 +39,6 @@
import android.graphics.RectF;
import android.graphics.Region;
import android.os.UserHandle;
-import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
@@ -54,16 +52,15 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherInitListenerEx;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.QuickstepAppTransitionManagerImpl;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.uioverrides.states.OverviewState;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
@@ -151,16 +148,10 @@
@NonNull
@Override
public RectF getWindowTargetRect() {
- final int halfIconSize = dp.iconSizePx / 2;
- final float targetCenterX = dp.availableWidthPx / 2f;
- final float targetCenterY = dp.availableHeightPx - dp.hotseatBarSizePx;
-
if (canUseWorkspaceView) {
return iconLocation;
} else {
- // Fallback to animate to center of screen.
- return new RectF(targetCenterX - halfIconSize, targetCenterY - halfIconSize,
- targetCenterX + halfIconSize, targetCenterY + halfIconSize);
+ return HomeAnimationFactory.getDefaultWindowTargetRect(dp);
}
}
@@ -175,18 +166,8 @@
@Override
public void playAtomicAnimation(float velocity) {
- // Setup workspace with 0 duration to prepare for our staggered animation.
- LauncherStateManager stateManager = activity.getStateManager();
- AnimatorSetBuilder builder = new AnimatorSetBuilder();
- // setRecentsAttachedToAppWindow() will animate recents out.
- builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
- stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 0);
- builder.build().start();
-
- // Stop scrolling so that it doesn't interfere with the translation offscreen.
- recentsView.getScroller().forceFinished(true);
-
- new StaggeredWorkspaceAnim(activity, workspaceView, velocity).start();
+ new StaggeredWorkspaceAnim(activity, velocity, true /* animateOverviewScrim */)
+ .start();
}
};
}
@@ -194,9 +175,6 @@
@Override
public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible,
boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "prepareRecentsUI");
- }
final LauncherState startState = activity.getStateManager().getState();
LauncherState resetState = startState;
@@ -211,11 +189,10 @@
// This ensures then the next swipe up to all-apps starts from scroll 0.
activity.getAppsView().reset(false /* animate */);
- // Optimization, hide the all apps view to prevent layout while initializing
- activity.getAppsView().getContentView().setVisibility(View.GONE);
-
return new AnimationFactory() {
- private ShelfAnimState mShelfState;
+ private final ShelfPeekAnim mShelfAnim =
+ ((QuickstepAppTransitionManagerImpl) activity.getAppTransitionManager())
+ .getShelfPeekAnim();
private boolean mIsAttachedToWindow;
@Override
@@ -244,30 +221,7 @@
@Override
public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator,
long duration) {
- if (mShelfState == shelfState) {
- return;
- }
- mShelfState = shelfState;
- activity.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM);
- if (mShelfState == ShelfAnimState.CANCEL) {
- return;
- }
- float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(activity);
- float shelfOverviewProgress = OVERVIEW.getVerticalProgress(activity);
- // Peek based on default overview progress so we can see hotseat if we're showing
- // that instead of predictions in overview.
- float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(activity);
- float shelfPeekingProgress = shelfHiddenProgress
- - (shelfHiddenProgress - defaultOverviewProgress) * 0.25f;
- float toProgress = mShelfState == ShelfAnimState.HIDE
- ? shelfHiddenProgress
- : mShelfState == ShelfAnimState.PEEK
- ? shelfPeekingProgress
- : shelfOverviewProgress;
- Animator shelfAnim = activity.getStateManager()
- .createStateElementAnimation(INDEX_SHELF_ANIM, toProgress);
- shelfAnim.setInterpolator(interpolator);
- shelfAnim.setDuration(duration).start();
+ mShelfAnim.setShelfState(shelfState, interpolator, duration);
}
@Override
@@ -282,22 +236,12 @@
INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
int runningTaskIndex = recentsView.getRunningTaskIndex();
- if (runningTaskIndex == 0) {
+ if (runningTaskIndex == recentsView.getTaskViewStartIndex()) {
// If we are on the first task (we haven't quick switched), translate recents in
// from the side. Calculate the start translation based on current scale/scroll.
float currScale = recentsView.getScaleX();
float scrollOffsetX = recentsView.getScrollOffset();
-
- float offscreenX = NORMAL.getOverviewScaleAndTranslation(activity).translationX;
- // The first task is hidden, so offset by its width.
- int firstTaskWidth = recentsView.getTaskViewAt(0).getWidth();
- offscreenX -= (firstTaskWidth + recentsView.getPageSpacing()) * currScale;
- // Offset since scale pushes tasks outwards.
- offscreenX += firstTaskWidth * (currScale - 1) / 2;
- offscreenX = Math.max(0, offscreenX);
- if (recentsView.isRtl()) {
- offscreenX = -offscreenX;
- }
+ float offscreenX = recentsView.getOffscreenTranslationX(currScale);
float fromTranslationX = attached ? offscreenX - scrollOffsetX : 0;
float toTranslationX = attached ? 0 : offscreenX - scrollOffsetX;
@@ -365,8 +309,7 @@
private void playScaleDownAnim(AnimatorSet anim, Launcher launcher, LauncherState fromState,
LauncherState endState) {
RecentsView recentsView = launcher.getOverviewPanel();
- TaskView v = recentsView.getTaskViewAt(recentsView.getCurrentPage());
- if (v == null) {
+ if (recentsView.getCurrentPageTaskView() == null) {
return;
}
@@ -394,7 +337,11 @@
// recents as a whole needs to translate further to keep up with the app window.
TaskView runningTaskView = recentsView.getRunningTaskView();
if (runningTaskView == null) {
- runningTaskView = recentsView.getTaskViewAt(recentsView.getCurrentPage());
+ runningTaskView = recentsView.getCurrentPageTaskView();
+ if (runningTaskView == null) {
+ // There are no task views in LockTask mode when Overview is enabled.
+ return;
+ }
}
TimeInterpolator oldInterpolator = translateY.getInterpolator();
Rect fallbackInsets = launcher.getDeviceProfile().getInsets();
@@ -493,4 +440,4 @@
public void onLaunchTaskSuccess(Launcher launcher) {
launcher.getStateManager().moveToRestState();
}
-}
\ No newline at end of file
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
index 6533c63..a8d402e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
@@ -15,8 +15,8 @@
*/
package com.android.quickstep;
-import static com.android.systemui.shared.system.ActivityManagerWrapper
- .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -25,11 +25,12 @@
import android.content.Context;
import android.os.Build;
import android.os.SystemClock;
+import android.util.Log;
import android.view.ViewConfiguration;
import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
import com.android.quickstep.views.RecentsView;
@@ -47,7 +48,6 @@
private final Context mContext;
private final ActivityManagerWrapper mAM;
private final RecentsModel mRecentsModel;
- private final MainThreadExecutor mMainThreadExecutor;
private final OverviewComponentObserver mOverviewComponentObserver;
private long mLastToggleTime;
@@ -55,7 +55,6 @@
public OverviewCommandHelper(Context context, OverviewComponentObserver observer) {
mContext = context;
mAM = ActivityManagerWrapper.getInstance();
- mMainThreadExecutor = new MainThreadExecutor();
mRecentsModel = RecentsModel.INSTANCE.get(mContext);
mOverviewComponentObserver = observer;
}
@@ -67,19 +66,19 @@
}
mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- mMainThreadExecutor.execute(new RecentsActivityCommand<>());
+ MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
}
public void onOverviewShown(boolean triggeredFromAltTab) {
- mMainThreadExecutor.execute(new ShowRecentsCommand(triggeredFromAltTab));
+ MAIN_EXECUTOR.execute(new ShowRecentsCommand(triggeredFromAltTab));
}
public void onOverviewHidden() {
- mMainThreadExecutor.execute(new HideRecentsCommand());
+ MAIN_EXECUTOR.execute(new HideRecentsCommand());
}
public void onTip(int actionType, int viewType) {
- mMainThreadExecutor.execute(() ->
+ MAIN_EXECUTOR.execute(() ->
UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType));
}
@@ -99,6 +98,7 @@
@Override
protected void onTransitionComplete() {
+ // TODO(b/138729100) This doesn't execute first time launcher is run
if (mTriggeredFromAltTab) {
RecentsView rv = (RecentsView) mHelper.getVisibleRecentsView();
if (rv == null) {
@@ -109,7 +109,7 @@
TaskView taskView = rv.getNextTaskView();
if (taskView == null) {
if (rv.getTaskViewCount() > 0) {
- taskView = (TaskView) rv.getPageAt(0);
+ taskView = rv.getTaskViewAt(0);
taskView.requestFocus();
} else {
rv.requestFocus();
@@ -177,7 +177,7 @@
// Otherwise, start overview.
mListener = mHelper.createActivityInitListener(this::onActivityReady);
mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
- this::createWindowAnimation, mContext, mMainThreadExecutor.getHandler(),
+ this::createWindowAnimation, mContext, MAIN_EXECUTOR.getHandler(),
mAnimationProvider.getRecentsLaunchDuration());
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
new file mode 100644
index 0000000..3c78dd8
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -0,0 +1,82 @@
+package com.android.quickstep;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.launcher3.testing.TestInformationHandler;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
+import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.views.RecentsView;
+
+import java.util.concurrent.ExecutionException;
+
+public class QuickstepTestInformationHandler extends TestInformationHandler {
+
+ public QuickstepTestInformationHandler(Context context) {
+ }
+
+ @Override
+ public Bundle call(String method) {
+ final Bundle response = new Bundle();
+ switch (method) {
+ case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
+ final float swipeHeight =
+ LayoutUtils.getDefaultSwipeHeight(mContext, mDeviceProfile);
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
+ return response;
+ }
+
+ case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: {
+ final float swipeHeight =
+ LayoutUtils.getShelfTrackingDistance(mContext, mDeviceProfile);
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
+ return response;
+ }
+
+ case TestProtocol.REQUEST_HOTSEAT_TOP: {
+ if (mLauncher == null) return null;
+
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+ PortraitStatesTouchController.getHotseatTop(mLauncher));
+ return response;
+ }
+
+ case TestProtocol.REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN: {
+ try {
+ final int leftMargin = MAIN_EXECUTOR.submit(() ->
+ mLauncher.<RecentsView>getOverviewPanel().getLeftGestureMargin()).get();
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, leftMargin);
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return response;
+ }
+
+ case TestProtocol.REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN: {
+ try {
+ final int rightMargin = MAIN_EXECUTOR.submit(() ->
+ mLauncher.<RecentsView>getOverviewPanel().getRightGestureMargin()).
+ get();
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, rightMargin);
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return response;
+ }
+ }
+
+ return super.call(method);
+ }
+
+ @Override
+ protected boolean isLauncherInitialized() {
+ return super.isLauncherInitialized() && TouchInteractionService.isInitialized();
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index fc29a56..9bdc98b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -28,8 +28,10 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.app.ActivityOptions;
+import android.content.Intent;
import android.content.res.Configuration;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.view.View;
@@ -42,7 +44,9 @@
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.fallback.RecentsRootView;
import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.ObjectWrapper;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
@@ -54,6 +58,9 @@
*/
public final class RecentsActivity extends BaseRecentsActivity {
+ public static final String EXTRA_THUMBNAIL = "thumbnailData";
+ public static final String EXTRA_TASK_ID = "taskID";
+
private Handler mUiHandler = new Handler(Looper.getMainLooper());
private RecentsRootView mRecentsRootView;
private FallbackRecentsView mFallbackRecentsView;
@@ -79,6 +86,22 @@
}
@Override
+ protected void onNewIntent(Intent intent) {
+ if (intent.getExtras() != null) {
+ int taskID = intent.getIntExtra(EXTRA_TASK_ID, 0);
+ IBinder thumbnail = intent.getExtras().getBinder(EXTRA_THUMBNAIL);
+ if (taskID != 0 && thumbnail instanceof ObjectWrapper) {
+ ThumbnailData thumbnailData = ((ObjectWrapper<ThumbnailData>) thumbnail).get();
+ mFallbackRecentsView.showCurrentTask(taskID);
+ mFallbackRecentsView.updateThumbnail(taskID, thumbnailData);
+ }
+ }
+ intent.removeExtra(EXTRA_TASK_ID);
+ intent.removeExtra(EXTRA_THUMBNAIL);
+ super.onNewIntent(intent);
+ }
+
+ @Override
protected void onHandleConfigChanged() {
super.onHandleConfigChanged();
mRecentsRootView.setup();
@@ -113,6 +136,12 @@
}
@Override
+ public void returnToHomescreen() {
+ super.returnToHomescreen();
+ // TODO(b/137318995) This should go home, but doing so removes freeform windows
+ }
+
+ @Override
public ActivityOptions getActivityLaunchOptions(final View v) {
if (!(v instanceof TaskView)) {
return null;
@@ -132,7 +161,7 @@
mFallbackRecentsView.resetViewUI();
}
});
- result.setAnimation(anim);
+ result.setAnimation(anim, RecentsActivity.this);
}
};
return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
@@ -178,6 +207,12 @@
mFallbackRecentsView.resetTaskVisuals();
}
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mFallbackRecentsView.reset();
+ }
+
public void onTaskLaunched() {
mFallbackRecentsView.resetTaskVisuals();
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
index ddd28a3..c4d3fa0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -19,12 +19,14 @@
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
-import static com.android.launcher3.Utilities.FLAG_NO_GESTURES;
-
+import android.os.SystemClock;
+import android.util.Log;
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import androidx.annotation.UiThread;
+
import com.android.launcher3.util.Preconditions;
import com.android.quickstep.inputconsumers.InputConsumer;
import com.android.quickstep.util.SwipeAnimationTargetSet;
@@ -33,13 +35,13 @@
import java.util.ArrayList;
import java.util.function.Supplier;
-import androidx.annotation.UiThread;
-
/**
* Wrapper around RecentsAnimationController to help with some synchronization
*/
public class RecentsAnimationWrapper {
+ private static final String TAG = "RecentsAnimationWrapper";
+
// A list of callbacks to run when we receive the recents animation target. There are different
// than the state callbacks as these run on the current worker thread.
private final ArrayList<Runnable> mCallbacks = new ArrayList<>();
@@ -127,6 +129,7 @@
boolean sendUserLeaveHint) {
SwipeAnimationTargetSet controller = targetSet;
targetSet = null;
+ disableInputProxy();
if (controller != null) {
controller.finishController(toRecents, onFinishComplete, sendUserLeaveHint);
}
@@ -155,6 +158,16 @@
mInputConsumerController.setInputListener(this::onInputConsumerEvent);
}
+ private void disableInputProxy() {
+ if (mInputConsumer != null && mTouchInProgress) {
+ long now = SystemClock.uptimeMillis();
+ MotionEvent dummyCancel = MotionEvent.obtain(now, now, ACTION_CANCEL, 0, 0, 0);
+ mInputConsumer.onMotionEvent(dummyCancel);
+ dummyCancel.recycle();
+ }
+ mInputConsumerController.setInputListener(null);
+ }
+
private boolean onInputConsumerEvent(InputEvent ev) {
if (ev instanceof MotionEvent) {
onInputConsumerMotionEvent((MotionEvent) ev);
@@ -170,6 +183,18 @@
private boolean onInputConsumerMotionEvent(MotionEvent ev) {
int action = ev.getAction();
+
+ // Just to be safe, verify that ACTION_DOWN comes before any other action,
+ // and ignore any ACTION_DOWN after the first one (though that should not happen).
+ if (!mTouchInProgress && action != ACTION_DOWN) {
+ Log.w(TAG, "Received non-down motion before down motion: " + action);
+ return false;
+ }
+ if (mTouchInProgress && action == ACTION_DOWN) {
+ Log.w(TAG, "Received down motion while touch was already in progress");
+ return false;
+ }
+
if (action == ACTION_DOWN) {
mTouchInProgress = true;
if (mInputConsumer == null) {
@@ -184,18 +209,15 @@
}
}
if (mInputConsumer != null) {
- int flags = ev.getEdgeFlags();
- ev.setEdgeFlags(flags | FLAG_NO_GESTURES);
mInputConsumer.onMotionEvent(ev);
- ev.setEdgeFlags(flags);
}
return true;
}
- public void setCancelWithDeferredScreenshot(boolean deferredWithScreenshot) {
+ public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
if (targetSet != null) {
- targetSet.controller.setCancelWithDeferredScreenshot(deferredWithScreenshot);
+ targetSet.controller.setDeferCancelUntilNextTransition(defer, screenshot);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
index c55f656..8783ee3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
@@ -15,7 +15,7 @@
*/
package com.android.quickstep;
-import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.util.Log;
@@ -25,6 +25,7 @@
import com.android.quickstep.util.RecentsAnimationListenerSet;
import com.android.quickstep.util.SwipeAnimationTargetSet;
import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
+
import java.io.PrintWriter;
/**
@@ -44,6 +45,7 @@
public boolean goingToLauncher;
public boolean recentsAnimationFinishInterrupted;
public int nextRunningTaskId = -1;
+ private int mLogId;
public void setOverviewComponentObserver(OverviewComponentObserver observer) {
mOverviewComponentObserver = observer;
@@ -77,7 +79,7 @@
mRecentsAnimationListener.removeListener(this);
mRecentsAnimationListener.cancelListener();
if (mLastAnimationRunning && mLastAnimationTarget != null) {
- Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(),
+ Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
finishAnimation
? mLastAnimationTarget::finishAnimation
: mLastAnimationTarget::cancelAnimation);
@@ -155,5 +157,10 @@
pw.println(prefix + "nextRunningTaskId=" + nextRunningTaskId);
pw.println(prefix + "lastAnimationCancelled=" + mLastAnimationCancelled);
pw.println(prefix + "lastAnimationRunning=" + mLastAnimationRunning);
+ pw.println(prefix + "logTraceId=" + mLogId);
+ }
+
+ public void setLogTraceId(int logId) {
+ this.mLogId = logId;
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index b90f6c2..17457aa 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -16,6 +16,8 @@
package com.android.quickstep;
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+
import android.graphics.Matrix;
import android.view.View;
@@ -47,8 +49,7 @@
};
public static final MainThreadInitializedObject<TaskOverlayFactory> INSTANCE =
- new MainThreadInitializedObject<>(c -> Overrides.getObject(TaskOverlayFactory.class,
- c, R.string.task_overlay_factory_class));
+ forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class);
public List<TaskSystemShortcut> getEnabledShortcuts(TaskView taskView) {
final ArrayList<TaskSystemShortcut> shortcuts = new ArrayList<>();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
index 213c5d3..1af0db0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
@@ -36,8 +36,6 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
@@ -207,8 +205,7 @@
}
};
WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
- future, animStartedListener, mHandler, true /* scaleUp */,
- v.getDisplay().getDisplayId());
+ future, animStartedListener, mHandler, true /* scaleUp */, displayId);
}
});
}
@@ -268,12 +265,16 @@
@Override
protected ActivityOptions makeLaunchOptions(Activity activity) {
- return ActivityOptionsCompat.makeFreeformOptions();
+ ActivityOptions activityOptions = ActivityOptionsCompat.makeFreeformOptions();
+ // Arbitrary bounds only because freeform is in dev mode right now
+ Rect r = new Rect(50, 50, 200, 200);
+ activityOptions.setLaunchBounds(r);
+ return activityOptions;
}
@Override
protected boolean onActivityStarted(BaseDraggingActivity activity) {
- Launcher.getLauncher(activity).getStateManager().goToState(LauncherState.NORMAL);
+ activity.returnToHomescreen();
return true;
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 53da0f9..2fa789f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -23,6 +23,8 @@
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.config.FeatureFlags.FAKE_LANDSCAPE_UI;
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
@@ -31,8 +33,10 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT;
import android.annotation.TargetApi;
import android.app.ActivityManager;
@@ -43,11 +47,10 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.Region;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
@@ -57,25 +60,26 @@
import android.text.TextUtils;
import android.util.Log;
import android.view.Choreographer;
-import android.view.Display;
import android.view.InputEvent;
import android.view.MotionEvent;
import android.view.Surface;
-import android.view.WindowManager;
import androidx.annotation.BinderThread;
import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.BaseFlags;
import com.android.launcher3.logging.EventLogArray;
import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.util.LooperExecutor;
-import com.android.launcher3.util.UiThreadHelper;
+import com.android.launcher3.model.AppLaunchTracker;
+import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.util.DefaultDisplay;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
@@ -88,6 +92,7 @@
import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer;
import com.android.quickstep.inputconsumers.ResetGestureInputConsumer;
import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
+import com.android.quickstep.util.AssistantUtilities;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -96,6 +101,7 @@
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
+import com.android.systemui.shared.system.RecentsAnimationListener;
import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat;
import java.io.FileDescriptor;
@@ -119,10 +125,6 @@
public String nextArg() {
return pollFirst().toLowerCase();
}
-
- public String nextArgExact() {
- return pollFirst();
- }
}
/**
@@ -130,17 +132,25 @@
*/
@TargetApi(Build.VERSION_CODES.Q)
public class TouchInteractionService extends Service implements
- NavigationModeChangeListener, DisplayListener {
+ NavigationModeChangeListener, DefaultDisplay.DisplayInfoChangeListener {
- public static final MainThreadExecutor MAIN_THREAD_EXECUTOR = new MainThreadExecutor();
- public static final LooperExecutor BACKGROUND_EXECUTOR =
- new LooperExecutor(UiThreadHelper.getBackgroundLooper());
+ /**
+ * NOTE: This value should be kept same as
+ * ActivityTaskManagerService#INTENT_EXTRA_LOG_TRACE_ID in platform
+ */
+ public static final String INTENT_EXTRA_LOG_TRACE_ID = "INTENT_EXTRA_LOG_TRACE_ID";
+
public static final EventLogArray TOUCH_INTERACTION_LOG =
new EventLogArray("touch_interaction_log", 40);
private static final String TAG = "TouchInteractionService";
+ private static final String KEY_BACK_NOTIFICATION_COUNT = "backNotificationCount";
+ private static final String NOTIFY_ACTION_BACK = "com.android.quickstep.action.BACK_GESTURE";
+ private static final int MAX_BACK_NOTIFICATION_COUNT = 3;
+ private int mBackGestureNotificationCounter = -1;
+
private final IBinder mMyBinder = new IOverviewProxy.Stub() {
public void onActiveNavBarRegionChanges(Region region) {
@@ -150,8 +160,10 @@
public void onInitialize(Bundle bundle) {
mISystemUiProxy = ISystemUiProxy.Stub
.asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
- MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
- MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet);
+ MAIN_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
+ MAIN_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet);
+ MAIN_EXECUTOR.execute(() -> preloadOverview(true /* fromInit */));
+ sIsInitialized = true;
}
@Override
@@ -185,7 +197,7 @@
@Override
public void onAssistantVisibilityChanged(float visibility) {
mLastAssistantVisibility = visibility;
- MAIN_THREAD_EXECUTOR.execute(
+ MAIN_EXECUTOR.execute(
TouchInteractionService.this::onAssistantVisibilityChanged);
}
@@ -199,11 +211,15 @@
mOverviewComponentObserver.getActivityControlHelper();
UserEventDispatcher.newInstance(getBaseContext()).logActionBack(completed, downX, downY,
isButton, gestureSwipeLeft, activityControl.getContainerType());
+
+ if (completed && !isButton && shouldNotifyBackGesture()) {
+ UI_HELPER_EXECUTOR.execute(TouchInteractionService.this::tryNotifyBackGesture);
+ }
}
public void onSystemUiStateChanged(int stateFlags) {
mSystemUiStateFlags = stateFlags;
- MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiFlagsChanged);
+ MAIN_EXECUTOR.execute(TouchInteractionService.this::onSystemUiFlagsChanged);
}
/** Deprecated methods **/
@@ -225,12 +241,18 @@
};
private static boolean sConnected = false;
+ private static boolean sIsInitialized = false;
private static final SwipeSharedState sSwipeSharedState = new SwipeSharedState();
+ private int mLogId;
public static boolean isConnected() {
return sConnected;
}
+ public static boolean isInitialized() {
+ return sIsInitialized;
+ }
+
public static SwipeSharedState getSwipeSharedState() {
return sSwipeSharedState;
}
@@ -238,6 +260,11 @@
private final InputConsumer mResetGestureInputConsumer =
new ResetGestureInputConsumer(sSwipeSharedState);
+ private final BaseSwipeUpHandler.Factory mWindowTreansformFactory =
+ this::createWindowTransformSwipeHandler;
+ private final BaseSwipeUpHandler.Factory mFallbackNoButtonFactory =
+ this::createFallbackNoButtonSwipeHandler;
+
private ActivityManagerWrapper mAM;
private RecentsModel mRecentsModel;
private ISystemUiProxy mISystemUiProxy;
@@ -296,8 +323,7 @@
registerReceiver(mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
}
- mDefaultDisplayId = getSystemService(WindowManager.class).getDefaultDisplay()
- .getDisplayId();
+ mDefaultDisplayId = DefaultDisplay.INSTANCE.get(this).getInfo().id;
String blockingActivity = getString(R.string.gesture_blocking_activity);
mGestureBlockingActivity = TextUtils.isEmpty(blockingActivity) ? null :
ComponentName.unflattenFromString(blockingActivity);
@@ -319,6 +345,9 @@
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
mInputEventReceiver = null;
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "disposeEventHandlers");
+ }
}
if (mInputMonitorCompat != null) {
mInputMonitorCompat.dispose();
@@ -327,16 +356,25 @@
}
private void initInputMonitor() {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 1");
+ }
if (!mMode.hasGestures || mISystemUiProxy == null) {
return;
}
disposeEventHandlers();
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 2");
+ }
try {
mInputMonitorCompat = InputMonitorCompat.fromBundle(mISystemUiProxy
.monitorGestureInput("swipe-up", mDefaultDisplayId), KEY_EXTRA_INPUT_MONITOR);
mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
mMainChoreographer, this::onInputEvent);
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 3");
+ }
} catch (RemoteException e) {
Log.e(TAG, "Unable to create input monitor", e);
}
@@ -352,9 +390,8 @@
return;
}
- Display defaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
- Point realSize = new Point();
- defaultDisplay.getRealSize(realSize);
+ DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(this).getInfo();
+ Point realSize = new Point(displayInfo.realSize);
mSwipeTouchRegion.set(0, 0, realSize.x, realSize.y);
if (mMode == Mode.NO_BUTTON) {
int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
@@ -376,7 +413,7 @@
} else {
mAssistantLeftRegion.setEmpty();
mAssistantRightRegion.setEmpty();
- switch (defaultDisplay.getRotation()) {
+ switch (displayInfo.rotation) {
case Surface.ROTATION_90:
mSwipeTouchRegion.left = mSwipeTouchRegion.right
- getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
@@ -394,12 +431,14 @@
@Override
public void onNavigationModeChanged(Mode newMode) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "onNavigationModeChanged " + newMode);
+ }
if (mMode.hasGestures != newMode.hasGestures) {
if (newMode.hasGestures) {
- getSystemService(DisplayManager.class).registerDisplayListener(
- this, MAIN_THREAD_EXECUTOR.getHandler());
+ DefaultDisplay.INSTANCE.get(this).addChangeListener(this);
} else {
- getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+ DefaultDisplay.INSTANCE.get(this).removeChangeListener(this);
}
}
mMode = newMode;
@@ -415,14 +454,8 @@
}
@Override
- public void onDisplayAdded(int i) { }
-
- @Override
- public void onDisplayRemoved(int i) { }
-
- @Override
- public void onDisplayChanged(int displayId) {
- if (displayId != mDefaultDisplayId) {
+ public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
+ if (info.id != mDefaultDisplayId) {
return;
}
@@ -448,6 +481,8 @@
// Temporarily disable model preload
// new ModelPreload().start(this);
+ mBackGestureNotificationCounter = Math.max(0, Utilities.getDevicePrefs(this)
+ .getInt(KEY_BACK_NOTIFICATION_COUNT, MAX_BACK_NOTIFICATION_COUNT));
Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
}
@@ -478,13 +513,14 @@
@Override
public void onDestroy() {
+ sIsInitialized = false;
if (mIsUserUnlocked) {
mInputConsumer.unregisterInputConsumer();
mOverviewComponentObserver.onDestroy();
}
disposeEventHandlers();
if (mMode.hasGestures) {
- getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+ DefaultDisplay.INSTANCE.get(this).removeChangeListener(this);
}
sConnected = false;
@@ -502,13 +538,19 @@
}
private void onInputEvent(InputEvent ev) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "onInputEvent " + ev);
+ }
if (!(ev instanceof MotionEvent)) {
Log.e(TAG, "Unknown event " + ev);
return;
}
+
MotionEvent event = (MotionEvent) ev;
- TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked());
if (event.getAction() == ACTION_DOWN) {
+ mLogId = TOUCH_INTERACTION_LOG.generateAndSetLogId();
+ sSwipeSharedState.setLogTraceId(mLogId);
+
if (mSwipeTouchRegion.contains(event.getX(), event.getY())) {
boolean useSharedState = mConsumer.useSharedSwipeState();
mConsumer.onConsumerAboutToBeSwitched();
@@ -521,19 +563,26 @@
// not interrupt it. QuickSwitch assumes that interruption can only happen if the
// next gesture is also quick switch.
mUncheckedConsumer =
- new AssistantTouchConsumer(this, mISystemUiProxy,
+ new AssistantTouchConsumer(
+ this,
+ mISystemUiProxy,
mOverviewComponentObserver.getActivityControlHelper(),
- InputConsumer.NO_OP, mInputMonitorCompat);
+ InputConsumer.NO_OP,
+ mInputMonitorCompat,
+ mOverviewComponentObserver.assistantGestureIsConstrained());
} else {
mUncheckedConsumer = InputConsumer.NO_OP;
}
}
+
+ TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked());
mUncheckedConsumer.onMotionEvent(event);
}
private boolean validSystemUiFlags() {
return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
&& (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
+ && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
&& ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
|| (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0);
}
@@ -553,7 +602,7 @@
if (isInValidSystemUiState) {
// This handles apps launched in direct boot mode (e.g. dialer) as well as apps
// launched while device is locked even after exiting direct boot mode (e.g. camera).
- return createDeviceLockedInputConsumer(mAM.getRunningTask(0));
+ return createDeviceLockedInputConsumer(mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT));
} else {
return mResetGestureInputConsumer;
}
@@ -567,8 +616,13 @@
final ActivityControlHelper activityControl =
mOverviewComponentObserver.getActivityControlHelper();
if (canTriggerAssistantAction(event)) {
- base = new AssistantTouchConsumer(this, mISystemUiProxy, activityControl, base,
- mInputMonitorCompat);
+ base = new AssistantTouchConsumer(
+ this,
+ mISystemUiProxy,
+ activityControl,
+ base,
+ mInputMonitorCompat,
+ mOverviewComponentObserver.assistantGestureIsConstrained());
}
if ((mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0) {
@@ -591,7 +645,7 @@
}
private InputConsumer newBaseConsumer(boolean useSharedState, MotionEvent event) {
- final RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
+ RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
if (!useSharedState) {
sSwipeSharedState.clearAllState(false /* finishAnimation */);
}
@@ -603,6 +657,17 @@
final ActivityControlHelper activityControl =
mOverviewComponentObserver.getActivityControlHelper();
+ boolean forceOverviewInputConsumer = false;
+ if (AssistantUtilities.isExcludedAssistant(runningTaskInfo)) {
+ // In the case where we are in the excluded assistant state, ignore it and treat the
+ // running activity as the task behind the assistant
+ runningTaskInfo = mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT /* ignoreActivityType */);
+ ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent();
+ ComponentName runningComponent = runningTaskInfo.baseIntent.getComponent();
+ forceOverviewInputConsumer =
+ runningComponent != null && runningComponent.equals(homeComponent);
+ }
+
if (runningTaskInfo == null && !sSwipeSharedState.goingToLauncher
&& !sSwipeSharedState.recentsAnimationFinishInterrupted) {
return mResetGestureInputConsumer;
@@ -612,17 +677,14 @@
RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
info.id = sSwipeSharedState.nextRunningTaskId;
return createOtherActivityInputConsumer(event, info);
- } else if (sSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
- return createOverviewInputConsumer(event);
+ } else if (sSwipeSharedState.goingToLauncher || activityControl.isResumed()
+ || forceOverviewInputConsumer) {
+ return createOverviewInputConsumer(event, forceOverviewInputConsumer);
} else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityControl.isInLiveTileMode()) {
- return createOverviewInputConsumer(event);
+ return createOverviewInputConsumer(event, forceOverviewInputConsumer);
} else if (mGestureBlockingActivity != null && runningTaskInfo != null
&& mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) {
return mResetGestureInputConsumer;
- } else if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
- return new FallbackNoButtonInputConsumer(this, activityControl,
- mInputMonitorCompat, sSwipeSharedState, mSwipeTouchRegion,
- mOverviewComponentObserver, disableHorizontalSwipe(event), runningTaskInfo);
} else {
return createOtherActivityInputConsumer(event, runningTaskInfo);
}
@@ -635,29 +697,38 @@
&& exclusionRegion.contains((int) event.getX(), (int) event.getY());
}
- private OtherActivityInputConsumer createOtherActivityInputConsumer(MotionEvent event,
+ private InputConsumer createOtherActivityInputConsumer(MotionEvent event,
RunningTaskInfo runningTaskInfo) {
- final ActivityControlHelper activityControl =
- mOverviewComponentObserver.getActivityControlHelper();
- boolean shouldDefer = activityControl.deferStartingActivity(mActiveNavBarRegion, event);
- return new OtherActivityInputConsumer(this, runningTaskInfo, mRecentsModel,
- mOverviewComponentObserver.getOverviewIntent(), activityControl,
- shouldDefer, mOverviewCallbacks, mInputConsumer, this::onConsumerInactive,
+ final boolean shouldDefer;
+ final BaseSwipeUpHandler.Factory factory;
+
+ if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
+ shouldDefer = !sSwipeSharedState.recentsAnimationFinishInterrupted;
+ factory = mFallbackNoButtonFactory;
+ } else {
+ shouldDefer = mOverviewComponentObserver.getActivityControlHelper()
+ .deferStartingActivity(mActiveNavBarRegion, event);
+ factory = mWindowTreansformFactory;
+ }
+
+ return new OtherActivityInputConsumer(this, runningTaskInfo,
+ shouldDefer, mOverviewCallbacks, this::onConsumerInactive,
sSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion,
- disableHorizontalSwipe(event));
+ disableHorizontalSwipe(event), factory, mLogId);
}
private InputConsumer createDeviceLockedInputConsumer(RunningTaskInfo taskInfo) {
if (mMode == Mode.NO_BUTTON && taskInfo != null) {
return new DeviceLockedInputConsumer(this, sSwipeSharedState, mInputMonitorCompat,
- mSwipeTouchRegion, taskInfo.taskId);
+ mSwipeTouchRegion, taskInfo.taskId, mLogId);
} else {
return mResetGestureInputConsumer;
}
}
- public InputConsumer createOverviewInputConsumer(MotionEvent event) {
+ public InputConsumer createOverviewInputConsumer(MotionEvent event,
+ boolean forceOverviewInputConsumer) {
final ActivityControlHelper activityControl =
mOverviewComponentObserver.getActivityControlHelper();
BaseDraggingActivity activity = activityControl.getCreatedActivity();
@@ -665,11 +736,14 @@
return mResetGestureInputConsumer;
}
- if (activity.getRootView().hasWindowFocus() || sSwipeSharedState.goingToLauncher) {
+ if (activity.getRootView().hasWindowFocus()
+ || sSwipeSharedState.goingToLauncher
+ || (BaseFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
+ && forceOverviewInputConsumer)) {
return new OverviewInputConsumer(activity, mInputMonitorCompat,
false /* startingInActivityBounds */);
} else {
- return new OverviewWithoutFocusInputConsumer(this, mInputMonitorCompat,
+ return new OverviewWithoutFocusInputConsumer(activity, mInputMonitorCompat,
disableHorizontalSwipe(event));
}
}
@@ -684,6 +758,61 @@
}
}
+ private void preloadOverview(boolean fromInit) {
+ if (!mIsUserUnlocked) {
+ return;
+ }
+ if (!mMode.hasGestures && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
+ // Prevent the overview from being started before the real home on first boot.
+ return;
+ }
+
+ if (RestoreDbTask.isPending(this)) {
+ // Preloading while a restore is pending may cause launcher to start the restore
+ // too early.
+ return;
+ }
+
+ final ActivityControlHelper<BaseDraggingActivity> activityControl =
+ mOverviewComponentObserver.getActivityControlHelper();
+ if (activityControl.getCreatedActivity() == null) {
+ // Make sure that UI states will be initialized.
+ activityControl.createActivityInitListener((activity, wasVisible) -> {
+ AppLaunchTracker.INSTANCE.get(activity);
+ return false;
+ }).register();
+ } else if (fromInit) {
+ // The activity has been created before the initialization of overview service. It is
+ // usually happens when booting or launcher is the top activity, so we should already
+ // have the latest state.
+ return;
+ }
+
+ // Pass null animation handler to indicate this start is preload.
+ startRecentsActivityAsync(mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState(),
+ null);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (!mIsUserUnlocked) {
+ return;
+ }
+ final ActivityControlHelper activityControl =
+ mOverviewComponentObserver.getActivityControlHelper();
+ final BaseDraggingActivity activity = activityControl.getCreatedActivity();
+ if (activity == null || activity.isStarted()) {
+ // We only care about the existing background activity.
+ return;
+ }
+ if (mOverviewComponentObserver.canHandleConfigChanges(activity.getComponentName(),
+ activity.getResources().getConfiguration().diff(newConfig))) {
+ return;
+ }
+
+ preloadOverview(false /* fromInit */);
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] rawArgs) {
if (rawArgs.length > 0 && Utilities.IS_DEBUG_DEVICE) {
@@ -708,8 +837,9 @@
pw.println(" assistantAvailable=" + mAssistantAvailable);
pw.println(" assistantDisabled="
+ QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
- pw.println(" resumed="
- + mOverviewComponentObserver.getActivityControlHelper().isResumed());
+ boolean resumed = mOverviewComponentObserver != null
+ && mOverviewComponentObserver.getActivityControlHelper().isResumed();
+ pw.println(" resumed=" + resumed);
pw.println(" useSharedState=" + mConsumer.useSharedSwipeState());
if (mConsumer.useSharedSwipeState()) {
sSwipeSharedState.dump(" ", pw);
@@ -739,4 +869,37 @@
break;
}
}
+
+ private BaseSwipeUpHandler createWindowTransformSwipeHandler(RunningTaskInfo runningTask,
+ long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
+ return new WindowTransformSwipeHandler(runningTask, this, touchTimeMs,
+ mOverviewComponentObserver, continuingLastGesture, mInputConsumer, mRecentsModel);
+ }
+
+ private BaseSwipeUpHandler createFallbackNoButtonSwipeHandler(RunningTaskInfo runningTask,
+ long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
+ return new FallbackNoButtonInputConsumer(this, mOverviewComponentObserver, runningTask,
+ mRecentsModel, mInputConsumer, isLikelyToStartNewTask, continuingLastGesture);
+ }
+
+ protected boolean shouldNotifyBackGesture() {
+ return mBackGestureNotificationCounter > 0 &&
+ mGestureBlockingActivity != null;
+ }
+
+ @WorkerThread
+ protected void tryNotifyBackGesture() {
+ if (shouldNotifyBackGesture()) {
+ mBackGestureNotificationCounter--;
+ Utilities.getDevicePrefs(this).edit()
+ .putInt(KEY_BACK_NOTIFICATION_COUNT, mBackGestureNotificationCounter).apply();
+ sendBroadcast(new Intent(NOTIFY_ACTION_BACK).setPackage(
+ mGestureBlockingActivity.getPackageName()));
+ }
+ }
+
+ public static void startRecentsActivityAsync(Intent intent, RecentsAnimationListener listener) {
+ UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
+ .startRecentsActivity(intent, null, listener, null, null));
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index f790c40..2fa4feb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -17,91 +17,72 @@
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
-import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import static com.android.launcher3.util.RaceConditionTracker.ENTER;
import static com.android.launcher3.util.RaceConditionTracker.EXIT;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
-import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
-import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.HIDE;
-import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.PEEK;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.HOME;
import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.NEW_TASK;
import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.RECENTS;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.Animator;
import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
+import android.content.Intent;
import android.graphics.Canvas;
-import android.graphics.Point;
import android.graphics.PointF;
-import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
import android.os.SystemClock;
-import android.util.Log;
-import android.view.HapticFeedbackConstants;
-import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnApplyWindowInsetsListener;
import android.view.ViewTreeObserver.OnDrawListener;
import android.view.WindowInsets;
-import android.view.WindowManager;
import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.RaceConditionTracker;
import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.views.FloatingIconView;
-import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
import com.android.quickstep.ActivityControlHelper.AnimationFactory;
-import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.inputconsumers.InputConsumer;
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
-import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.ClipAnimationHelper.TargetAlphaProvider;
import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
import com.android.quickstep.util.SwipeAnimationTargetSet;
-import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
import com.android.quickstep.views.LiveTileOverlay;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -109,19 +90,14 @@
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
import com.android.systemui.shared.system.WindowCallbacksCompat;
-import java.util.function.BiFunction;
-import java.util.function.Consumer;
-
@TargetApi(Build.VERSION_CODES.O)
public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
- implements SwipeAnimationListener, OnApplyWindowInsetsListener {
+ extends BaseSwipeUpHandler<T, RecentsView>
+ implements OnApplyWindowInsetsListener {
private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
- private static final Rect TEMP_RECT = new Rect();
-
private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
private static int getFlagForIndex(int index, String name) {
@@ -217,67 +193,31 @@
Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured";
- private static final long SHELF_ANIM_DURATION = 240;
public static final long RECENTS_ATTACH_DURATION = 300;
- // Start resisting when swiping past this factor of mTransitionDragLength.
- private static final float DRAG_LENGTH_FACTOR_START_PULLBACK = 1.4f;
- // This is how far down we can scale down, where 0f is full screen and 1f is recents.
- private static final float DRAG_LENGTH_FACTOR_MAX_PULLBACK = 1.8f;
- private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
-
/**
* Used as the page index for logging when we return to the last task at the end of the gesture.
*/
private static final int LOG_NO_OP_PAGE_INDEX = -1;
- private final ClipAnimationHelper mClipAnimationHelper;
- private final ClipAnimationHelper.TransformParams mTransformParams;
-
- private Runnable mGestureEndCallback;
private GestureEndTarget mGestureEndTarget;
// Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
private RunningWindowAnim mRunningWindowAnim;
private boolean mIsShelfPeeking;
- private DeviceProfile mDp;
- // The distance needed to drag to reach the task size in recents.
- private int mTransitionDragLength;
- // How much further we can drag past recents, as a factor of mTransitionDragLength.
- private float mDragLengthFactor = 1;
- // Shift in the range of [0, 1].
- // 0 => preview snapShot is completely visible, and hotseat is completely translated down
- // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
- // visible.
- private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
private boolean mContinuingLastGesture;
- // To avoid UI jump when gesture is started, we offset the animation by the threshold.
- private float mShiftAtGestureStart = 0;
- private final Handler mMainThreadHandler = MAIN_THREAD_EXECUTOR.getHandler();
-
- private final Context mContext;
- private final ActivityControlHelper<T> mActivityControlHelper;
- private final ActivityInitListener mActivityInitListener;
-
- private final SysUINavigationMode.Mode mMode;
-
- private final int mRunningTaskId;
private ThumbnailData mTaskSnapshot;
- private MultiStateCallback mStateCallback;
// Used to control launcher components throughout the swipe gesture.
private AnimatorPlaybackController mLauncherTransitionController;
private boolean mHasLauncherTransitionControllerStarted;
- private T mActivity;
- private RecentsView mRecentsView;
private AnimationFactory mAnimationFactory = (t) -> { };
private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay();
+ private boolean mLiveTileOverlayAttached = false;
- private boolean mCanceled;
private boolean mWasLauncherAlreadyVisible;
- private int mFinishingRecentsAnimationForNewTaskId = -1;
private boolean mPassedOverviewThreshold;
private boolean mGestureStarted;
@@ -286,31 +226,17 @@
private PointF mDownPos;
private boolean mIsLikelyToStartNewTask;
- private final RecentsAnimationWrapper mRecentsAnimationWrapper;
-
private final long mTouchTimeMs;
private long mLauncherFrameDrawnTime;
public WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context,
- long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture,
- InputConsumerController inputConsumer) {
- mContext = context;
- mRunningTaskId = runningTaskInfo.id;
+ long touchTimeMs, OverviewComponentObserver overviewComponentObserver,
+ boolean continuingLastGesture,
+ InputConsumerController inputConsumer, RecentsModel recentsModel) {
+ super(context, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id);
mTouchTimeMs = touchTimeMs;
- mActivityControlHelper = controller;
- mActivityInitListener = mActivityControlHelper
- .createActivityInitListener(this::onActivityInit);
mContinuingLastGesture = continuingLastGesture;
- mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
- this::createNewInputProxyHandler);
- mClipAnimationHelper = new ClipAnimationHelper(context);
- mTransformParams = new ClipAnimationHelper.TransformParams();
-
- mMode = SysUINavigationMode.getMode(context);
initStateCallbacks();
-
- DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
- initTransitionEndpoints(dp);
}
private void initStateCallbacks() {
@@ -373,44 +299,8 @@
}
}
- private void setStateOnUiThread(int stateFlag) {
- if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
- mStateCallback.setState(stateFlag);
- } else {
- postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag));
- }
- }
-
- private void initTransitionEndpoints(DeviceProfile dp) {
- mDp = dp;
-
- Rect tempRect = new Rect();
- mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
- dp, mContext, tempRect);
- mClipAnimationHelper.updateTargetRect(tempRect);
- if (mMode == Mode.NO_BUTTON) {
- // We can drag all the way to the top of the screen.
- mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
- }
- }
-
- private long getFadeInDuration() {
- if (mCurrentShift.getCurrentAnimation() != null) {
- ObjectAnimator anim = mCurrentShift.getCurrentAnimation();
- long theirDuration = anim.getDuration() - anim.getCurrentPlayTime();
-
- // TODO: Find a better heuristic
- return Math.min(MAX_SWIPE_DURATION, Math.max(theirDuration, MIN_SWIPE_DURATION));
- } else {
- return MAX_SWIPE_DURATION;
- }
- }
-
- public void initWhenReady() {
- mActivityInitListener.register();
- }
-
- private boolean onActivityInit(final T activity, Boolean alreadyOnHome) {
+ @Override
+ protected boolean onActivityInit(final T activity, Boolean alreadyOnHome) {
if (mActivity == activity) {
return true;
}
@@ -431,21 +321,8 @@
}
mRecentsView = activity.getOverviewPanel();
- SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> {
- mTransformParams.setSyncTransactionApplier(applier);
- mRecentsAnimationWrapper.runOnInit(() ->
- mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier));
- });
-
- mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
- if (mGestureEndTarget != HOME) {
- updateFinalShift();
- }
- });
- mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
- mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
- mRecentsView.setLiveTileOverlay(mLiveTileOverlay);
- mActivity.getRootView().getOverlay().add(mLiveTileOverlay);
+ linkRecentsViewScroll();
+ addLiveTileOverlay();
mStateCallback.setState(STATE_LAUNCHER_PRESENT);
if (alreadyOnHome) {
@@ -458,33 +335,23 @@
return true;
}
+ @Override
+ protected boolean moveWindowWithRecentsScroll() {
+ return mGestureEndTarget != HOME;
+ }
+
private void onLauncherStart(final T activity) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart");
- }
if (mActivity != activity) {
return;
}
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 1");
- }
if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
return;
}
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 2");
- }
// If we've already ended the gesture and are going home, don't prepare recents UI,
// as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
if (mGestureEndTarget != HOME) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 3");
- }
Runnable initAnimFactory = () -> {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 4");
- }
mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
mWasLauncherAlreadyVisible, true,
this::onAnimatorPlaybackControllerCreated);
@@ -494,14 +361,8 @@
// Launcher is visible, but might be about to stop. Thus, if we prepare recents
// now, it might get overridden by moveToRestState() in onStop(). To avoid this,
// wait until the next gesture (and possibly launcher) starts.
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 5");
- }
mStateCallback.addCallback(STATE_GESTURE_STARTED, initAnimFactory);
} else {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 6");
- }
initAnimFactory.run();
}
}
@@ -577,32 +438,9 @@
return TaskView.getCurveScaleForInterpolation(interpolation);
}
- public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode rotationMode) {
- return mRecentsView != null ? mRecentsView.getEventDispatcher(rotationMode) : null;
- }
-
- @UiThread
- public void updateDisplacement(float displacement) {
- // We are moving in the negative x/y direction
- displacement = -displacement;
- if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) {
- mCurrentShift.updateValue(mDragLengthFactor);
- } else {
- float translation = Math.max(displacement, 0);
- float shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
- if (shift > DRAG_LENGTH_FACTOR_START_PULLBACK) {
- float pullbackProgress = Utilities.getProgress(shift,
- DRAG_LENGTH_FACTOR_START_PULLBACK, mDragLengthFactor);
- pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
- shift = DRAG_LENGTH_FACTOR_START_PULLBACK + pullbackProgress
- * (DRAG_LENGTH_FACTOR_MAX_PULLBACK - DRAG_LENGTH_FACTOR_START_PULLBACK);
- }
- mCurrentShift.updateValue(shift);
- }
- }
-
+ @Override
public void onMotionPauseChanged(boolean isPaused) {
- setShelfState(isPaused ? PEEK : HIDE, OVERSHOOT_1_2, SHELF_ANIM_DURATION);
+ setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION);
}
public void maybeUpdateRecentsAttachedState() {
@@ -639,8 +477,8 @@
recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
if (animate) {
// Only animate if an adjacent task view is visible on screen.
- TaskView adjacentTask1 = mRecentsView.getTaskViewAt(runningTaskIndex + 1);
- TaskView adjacentTask2 = mRecentsView.getTaskViewAt(runningTaskIndex - 1);
+ TaskView adjacentTask1 = mRecentsView.getNextTaskView();
+ TaskView adjacentTask2 = mRecentsView.getPreviousTaskView();
float prevTranslationX = mRecentsView.getTranslationX();
mRecentsView.setTranslationX(0);
animate = (adjacentTask1 != null && adjacentTask1.getGlobalVisibleRect(TEMP_RECT))
@@ -651,6 +489,7 @@
mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
}
+ @Override
public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) {
mIsLikelyToStartNewTask = isLikelyToStartNewTask;
@@ -666,9 +505,8 @@
if (mIsShelfPeeking != wasShelfPeeking) {
maybeUpdateRecentsAttachedState();
}
- if (mRecentsView != null && shelfState.shouldPreformHaptic) {
- mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ if (shelfState.shouldPreformHaptic) {
+ performHapticFeedback();
}
}
@@ -697,19 +535,18 @@
updateLauncherTransitionProgress();
}
- @UiThread
- private void updateFinalShift() {
- float shift = mCurrentShift.value;
+ @Override
+ public Intent getLaunchIntent() {
+ return mOverviewComponentObserver.getOverviewIntent();
+ }
+
+ @Override
+ public void updateFinalShift() {
SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
if (controller != null) {
- float offsetX = mRecentsView == null ? 0 : mRecentsView.getScrollOffset();
- float offsetScale = getTaskCurveScaleForOffsetX(offsetX,
- mClipAnimationHelper.getTargetRect().width());
- mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale);
- mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
- mTransformParams);
- updateSysUiFlags(shift);
+ applyTransformUnchecked();
+ updateSysUiFlags(mCurrentShift.value);
}
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
@@ -722,9 +559,8 @@
final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
if (passed != mPassedOverviewThreshold) {
mPassedOverviewThreshold = passed;
- if (mRecentsView != null && mMode != Mode.NO_BUTTON) {
- mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ if (mMode != Mode.NO_BUTTON) {
+ performHapticFeedback();
}
}
@@ -742,9 +578,7 @@
// Normalize the progress to 0 to 1, as the animation controller will clamp it to that
// anyway. The controller mimics the drag length factor by applying it to its interpolators.
float progress = mCurrentShift.value / mDragLengthFactor;
- mLauncherTransitionController.setPlayFraction(
- progress <= mShiftAtGestureStart || mShiftAtGestureStart >= 1
- ? 0 : (progress - mShiftAtGestureStart) / (1 - mShiftAtGestureStart));
+ mLauncherTransitionController.setPlayFraction(progress);
}
/**
@@ -752,8 +586,7 @@
*/
private void updateSysUiFlags(float windowProgress) {
if (mRecentsView != null) {
- TaskView centermostTask = mRecentsView.getTaskViewAt(mRecentsView
- .getPageNearestToCenterOfScreen());
+ TaskView centermostTask = mRecentsView.getTaskViewNearestToCenterOfScreen();
int centermostTaskFlags = centermostTask == null ? 0
: centermostTask.getThumbnail().getSysUiStatusNavFlags();
boolean useHomeScreenFlags = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
@@ -767,39 +600,7 @@
@Override
public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
- DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
- final Rect overviewStackBounds;
- RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
-
- if (targetSet.minimizedHomeBounds != null && runningTaskTarget != null) {
- overviewStackBounds = mActivityControlHelper
- .getOverviewWindowBounds(targetSet.minimizedHomeBounds, runningTaskTarget);
- dp = dp.getMultiWindowProfile(mContext, new Point(
- targetSet.minimizedHomeBounds.width(), targetSet.minimizedHomeBounds.height()));
- dp.updateInsets(targetSet.homeContentInsets);
- } else {
- if (mActivity != null) {
- int loc[] = new int[2];
- View rootView = mActivity.getRootView();
- rootView.getLocationOnScreen(loc);
- overviewStackBounds = new Rect(loc[0], loc[1], loc[0] + rootView.getWidth(),
- loc[1] + rootView.getHeight());
- } else {
- overviewStackBounds = new Rect(0, 0, dp.widthPx, dp.heightPx);
- }
- // If we are not in multi-window mode, home insets should be same as system insets.
- dp = dp.copy(mContext);
- dp.updateInsets(targetSet.homeContentInsets);
- }
- dp.updateIsSeascape(mContext.getSystemService(WindowManager.class));
-
- if (runningTaskTarget != null) {
- mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
- }
- mClipAnimationHelper.prepareAnimation(dp, false /* isOpening */);
- initTransitionEndpoints(dp);
-
- mRecentsAnimationWrapper.setController(targetSet);
+ super.onRecentsAnimationStart(targetSet);
TOUCH_INTERACTION_LOG.addLog("startRecentsAnimationCallback", targetSet.apps.length);
setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
@@ -814,10 +615,9 @@
TOUCH_INTERACTION_LOG.addLog("cancelRecentsAnimation");
}
- @UiThread
+ @Override
public void onGestureStarted() {
notifyGestureStartedAsync();
- mShiftAtGestureStart = mCurrentShift.value;
setStateOnUiThread(STATE_GESTURE_STARTED);
mGestureStarted = true;
}
@@ -838,7 +638,7 @@
/**
* Called as a result on ACTION_CANCEL to return the UI to the start state.
*/
- @UiThread
+ @Override
public void onGestureCancelled() {
updateDisplacement(0);
setStateOnUiThread(STATE_GESTURE_COMPLETED);
@@ -851,7 +651,7 @@
* @param velocity The x and y components of the velocity when the gesture ends.
* @param downPos The x and y value of where the gesture started.
*/
- @UiThread
+ @Override
public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
@@ -869,9 +669,9 @@
handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
}
- @UiThread
- private InputConsumer createNewInputProxyHandler() {
- endRunningWindowAnim();
+ @Override
+ protected InputConsumer createNewInputProxyHandler() {
+ endRunningWindowAnim(mGestureEndTarget == HOME /* cancel */);
endLauncherTransitionController();
if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
// Hide the task view, if not already hidden
@@ -883,9 +683,13 @@
? InputConsumer.NO_OP : new OverviewInputConsumer(activity, null, true);
}
- private void endRunningWindowAnim() {
+ private void endRunningWindowAnim(boolean cancel) {
if (mRunningWindowAnim != null) {
- mRunningWindowAnim.end();
+ if (cancel) {
+ mRunningWindowAnim.cancel();
+ } else {
+ mRunningWindowAnim.end();
+ }
}
}
@@ -971,14 +775,14 @@
interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL;
} else {
startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
- * SINGLE_FRAME_MS / mTransitionDragLength, 0, mDragLengthFactor);
+ * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
float minFlingVelocity = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_min_velocity);
if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
if (endTarget == RECENTS && mMode != Mode.NO_BUTTON) {
Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
- startShift, endShift, endShift, velocityPxPerMs.y,
- mTransitionDragLength);
+ startShift, endShift, endShift, endVelocity / 1000,
+ mTransitionDragLength, mContext);
endShift = overshoot.end;
interpolator = overshoot.interpolator;
duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
@@ -1113,7 +917,15 @@
windowAnim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
- setStateOnUiThread(target.endState);
+ if (target == NEW_TASK && mRecentsView != null
+ && mRecentsView.getNextPage() == mRecentsView.getRunningTaskIndex()) {
+ // We are about to launch the current running task, so use LAST_TASK state
+ // instead of NEW_TASK. This could happen, for example, if our scroll is
+ // aborted after we determined the target to be NEW_TASK.
+ setStateOnUiThread(LAST_TASK.endState);
+ } else {
+ setStateOnUiThread(target.endState);
+ }
}
});
windowAnim.start();
@@ -1152,65 +964,22 @@
* @param startProgress The progress of {@link #mCurrentShift} to start the window from.
* @param homeAnimationFactory The home animation factory.
*/
- private RectFSpringAnim createWindowAnimationToHome(float startProgress,
+ @Override
+ protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
HomeAnimationFactory homeAnimationFactory) {
- final RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet;
- final RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet,
- mTransformParams.setProgress(startProgress), false /* launcherOnTop */));
- final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
-
- final View floatingView = homeAnimationFactory.getFloatingView();
- final boolean isFloatingIconView = floatingView instanceof FloatingIconView;
- RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mActivity.getResources());
- if (isFloatingIconView) {
- FloatingIconView fiv = (FloatingIconView) floatingView;
- anim.addAnimatorListener(fiv);
- fiv.setOnTargetChangeListener(anim::onTargetPositionChanged);
- }
-
- AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome();
-
- // End on a "round-enough" radius so that the shape reveal doesn't have to do too much
- // rounding at the end of the animation.
- float startRadius = mClipAnimationHelper.getCurrentCornerRadius();
- float endRadius = startRect.width() / 6f;
- // We want the window alpha to be 0 once this threshold is met, so that the
- // FolderIconView can be seen morphing into the icon shape.
- final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f;
- anim.addOnUpdateListener((currentRect, progress) -> {
- homeAnim.setPlayFraction(progress);
-
- float alphaProgress = ACCEL_1_5.getInterpolation(progress);
- float windowAlpha = Utilities.boundToRange(Utilities.mapToRange(alphaProgress, 0,
- windowAlphaThreshold, 1.5f, 0f, Interpolators.LINEAR), 0, 1);
- mTransformParams.setProgress(progress)
- .setCurrentRectAndTargetAlpha(currentRect, windowAlpha);
- if (isFloatingIconView) {
- mTransformParams.setCornerRadius(endRadius * progress + startRadius
- * (1f - progress));
- }
- mClipAnimationHelper.applyTransform(targetSet, mTransformParams,
- false /* launcherOnTop */);
-
- if (isFloatingIconView) {
- ((FloatingIconView) floatingView).update(currentRect, 1f, progress,
- windowAlphaThreshold, mClipAnimationHelper.getCurrentCornerRadius(), false);
- }
-
- updateSysUiFlags(Math.max(progress, mCurrentShift.value));
- });
+ RectFSpringAnim anim =
+ super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
+ anim.addOnUpdateListener((r, p) -> updateSysUiFlags(Math.max(p, mCurrentShift.value)));
anim.addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationStart(Animator animation) {
- homeAnim.dispatchOnStart();
if (mActivity != null) {
- mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
+ removeLiveTileOverlay();
}
}
@Override
public void onAnimationSuccess(Animator animator) {
- homeAnim.getAnimationPlayer().end();
if (mRecentsView != null) {
mRecentsView.post(mRecentsView::resetTaskVisuals);
}
@@ -1222,11 +991,18 @@
return anim;
}
- /**
- * @return The GestureEndTarget if the gesture has ended, else null.
- */
- public @Nullable GestureEndTarget getGestureEndTarget() {
- return mGestureEndTarget;
+ @Override
+ public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) {
+ if (mGestureEndTarget != null) {
+ sharedState.canGestureBeContinued = mGestureEndTarget.canBeContinued;
+ sharedState.goingToLauncher = mGestureEndTarget.isLauncher;
+ }
+
+ if (sharedState.canGestureBeContinued) {
+ cancelCurrentAnimation(sharedState);
+ } else {
+ reset();
+ }
}
@UiThread
@@ -1239,43 +1015,18 @@
@UiThread
private void startNewTask() {
- // Launch the task user scrolled to (mRecentsView.getNextPage()).
- if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- // We finish recents animation inside launchTask() when live tile is enabled.
- mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false /* animate */,
- true /* freezeTaskList */);
- } else {
- int taskId = mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).getTask().key.id;
- mFinishingRecentsAnimationForNewTaskId = taskId;
- mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
- if (!mCanceled) {
- TaskView nextTask = mRecentsView.getTaskView(taskId);
- if (nextTask != null) {
- nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
- success -> {
- if (!success) {
- // We couldn't launch the task, so take user to overview so they can
- // decide what to do instead of staying in this broken state.
- endLauncherTransitionController();
- mActivityControlHelper.onLaunchTaskFailed(mActivity);
- nextTask.notifyTaskLaunchFailed(TAG);
- updateSysUiFlags(1 /* windowProgress == overview */);
- } else {
- mActivityControlHelper.onLaunchTaskSuccess(mActivity);
- }
- }, mMainThreadHandler);
- doLogGesture(NEW_TASK);
- }
- reset();
- }
- mCanceled = false;
- mFinishingRecentsAnimationForNewTaskId = -1;
- });
- }
- TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
+ startNewTask(STATE_HANDLER_INVALIDATED, success -> {
+ if (!success) {
+ // We couldn't launch the task, so take user to overview so they can
+ // decide what to do instead of staying in this broken state.
+ endLauncherTransitionController();
+ updateSysUiFlags(1 /* windowProgress == overview */);
+ }
+ doLogGesture(NEW_TASK);
+ });
}
- public void reset() {
+ private void reset() {
setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
@@ -1283,7 +1034,7 @@
* Cancels any running animation so that the active target can be overriden by a new swipe
* handle (in case of quick switch).
*/
- public void cancelCurrentAnimation(SwipeSharedState sharedState) {
+ private void cancelCurrentAnimation(SwipeSharedState sharedState) {
mCanceled = true;
mCurrentShift.cancelAnimation();
if (mLauncherTransitionController != null && mLauncherTransitionController
@@ -1306,7 +1057,7 @@
}
private void invalidateHandler() {
- endRunningWindowAnim();
+ endRunningWindowAnim(false /* cancel */);
if (mGestureEndCallback != null) {
mGestureEndCallback.run();
@@ -1322,7 +1073,7 @@
mRecentsView.onGestureAnimationEnd();
mActivity.getRootView().setOnApplyWindowInsetsListener(null);
- mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
+ removeLiveTileOverlay();
}
private void endLauncherTransitionController() {
@@ -1437,7 +1188,8 @@
private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
endLauncherTransitionController();
mActivityControlHelper.onSwipeUpToRecentsComplete(mActivity);
- mRecentsAnimationWrapper.setCancelWithDeferredScreenshot(true);
+ mRecentsAnimationWrapper.setDeferCancelUntilNextTransition(true /* defer */,
+ true /* screenshot */);
mRecentsView.onSwipeUpAnimationSuccess();
RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
@@ -1446,17 +1198,28 @@
reset();
}
- public void setGestureEndCallback(Runnable gestureEndCallback) {
- mGestureEndCallback = gestureEndCallback;
- }
-
- private void setTargetAlphaProvider(
- BiFunction<RemoteAnimationTargetCompat, Float, Float> provider) {
+ private void setTargetAlphaProvider(TargetAlphaProvider provider) {
mClipAnimationHelper.setTaskAlphaCallback(provider);
updateFinalShift();
}
- public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, Float expectedAlpha) {
+ private synchronized void addLiveTileOverlay() {
+ if (!mLiveTileOverlayAttached) {
+ mActivity.getRootView().getOverlay().add(mLiveTileOverlay);
+ mRecentsView.setLiveTileOverlay(mLiveTileOverlay);
+ mLiveTileOverlayAttached = true;
+ }
+ }
+
+ private synchronized void removeLiveTileOverlay() {
+ if (mLiveTileOverlayAttached) {
+ mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
+ mRecentsView.setLiveTileOverlay(null);
+ mLiveTileOverlayAttached = false;
+ }
+ }
+
+ public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, float expectedAlpha) {
if (!isNotInRecents(app)) {
return 0;
}
@@ -1467,16 +1230,4 @@
return app.isNotInRecents
|| app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
}
-
- private interface RunningWindowAnim {
- void end();
-
- static RunningWindowAnim wrap(Animator animator) {
- return animator::end;
- }
-
- static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) {
- return rectFSpringAnim::end;
- }
- }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
index c2876180..7f1aae5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
@@ -31,6 +32,10 @@
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+
+import java.util.ArrayList;
public class FallbackRecentsView extends RecentsView<RecentsActivity> {
@@ -54,6 +59,8 @@
private float mZoomScale = 1f;
private float mZoomTranslationY = 0f;
+ private RunningTaskInfo mRunningTaskInfo;
+
public FallbackRecentsView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -88,6 +95,12 @@
}
@Override
+ public void reset() {
+ super.reset();
+ resetViewUI();
+ }
+
+ @Override
protected void getTaskSize(DeviceProfile dp, Rect outRect) {
LayoutUtils.calculateFallbackTaskSize(getContext(), dp, outRect);
}
@@ -115,6 +128,12 @@
}
@Override
+ public void resetTaskVisuals() {
+ super.resetTaskVisuals();
+ setFullscreenProgress(mFullscreenProgress);
+ }
+
+ @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
@@ -139,4 +158,41 @@
TRANSLATION_Y.set(this, Utilities.mapRange(mZoomInProgress, 0, mZoomTranslationY));
FULLSCREEN_PROGRESS.set(this, mZoomInProgress);
}
+
+ public void onGestureAnimationStart(RunningTaskInfo runningTaskInfo) {
+ mRunningTaskInfo = runningTaskInfo;
+ onGestureAnimationStart(runningTaskInfo == null ? -1 : runningTaskInfo.taskId);
+ }
+
+ @Override
+ public void setCurrentTask(int runningTaskId) {
+ super.setCurrentTask(runningTaskId);
+ if (mRunningTaskInfo != null && mRunningTaskInfo.taskId != runningTaskId) {
+ mRunningTaskInfo = null;
+ }
+ }
+
+ @Override
+ protected void applyLoadPlan(ArrayList<Task> tasks) {
+ // When quick-switching on 3p-launcher, we add a "dummy" tile corresponding to Launcher
+ // as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
+ // track the index of the next task appropriately, as if we are switching on any other app.
+ if (mRunningTaskInfo != null && mRunningTaskInfo.taskId == mRunningTaskId) {
+ // Check if the task list has running task
+ boolean found = false;
+ for (Task t : tasks) {
+ if (t.key.id == mRunningTaskId) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ ArrayList<Task> newList = new ArrayList<>(tasks.size() + 1);
+ newList.addAll(tasks);
+ newList.add(Task.from(new TaskKey(mRunningTaskInfo), mRunningTaskInfo, false));
+ tasks = newList;
+ }
+ }
+ super.applyLoadPlan(tasks);
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
index 1820729..2b369e7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
@@ -80,7 +80,8 @@
if (!insets.equals(mInsets)) {
super.setInsets(insets);
}
- setBackground(insets.top == 0 ? null
+ setBackground(insets.top == 0 || !mAllowSysuiScrims
+ ? null
: Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
index 38b5a13..d4cdaf0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
@@ -1,3 +1,4 @@
+
/*
* Copyright (C) 2019 The Android Open Source Project
*
@@ -82,22 +83,26 @@
private int mDirection;
private ActivityControlHelper mActivityControlHelper;
- private final float mDistThreshold;
+ private final float mDragDistThreshold;
+ private final float mFlingDistThreshold;
private final long mTimeThreshold;
private final int mAngleThreshold;
private final float mSquaredSlop;
private final ISystemUiProxy mSysUiProxy;
private final Context mContext;
private final GestureDetector mGestureDetector;
+ private final boolean mIsAssistGestureConstrained;
public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy,
ActivityControlHelper activityControlHelper, InputConsumer delegate,
- InputMonitorCompat inputMonitor) {
+ InputMonitorCompat inputMonitor, boolean isAssistGestureConstrained) {
super(delegate, inputMonitor);
final Resources res = context.getResources();
mContext = context;
mSysUiProxy = systemUiProxy;
- mDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
+ mIsAssistGestureConstrained = isAssistGestureConstrained;
+ mDragDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
+ mFlingDistThreshold = res.getDimension(R.dimen.gestures_assistant_fling_threshold);
mTimeThreshold = res.getInteger(R.integer.assistant_gesture_min_time_threshold);
mAngleThreshold = res.getInteger(R.integer.assistant_gesture_corner_deg_threshold);
@@ -117,8 +122,6 @@
@Override
public void onMotionEvent(MotionEvent ev) {
// TODO add logging
- mGestureDetector.onTouchEvent(ev);
-
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
mActivePointerId = ev.getPointerId(0);
@@ -213,6 +216,8 @@
break;
}
+ mGestureDetector.onTouchEvent(ev);
+
if (mState != STATE_ACTIVE) {
mDelegate.onMotionEvent(ev);
}
@@ -220,9 +225,9 @@
private void updateAssistantProgress() {
if (!mLaunchedAssistant) {
- mLastProgress = Math.min(mDistance * 1f / mDistThreshold, 1) * mTimeFraction;
+ mLastProgress = Math.min(mDistance * 1f / mDragDistThreshold, 1) * mTimeFraction;
try {
- if (mDistance >= mDistThreshold && mTimeFraction >= 1) {
+ if (mDistance >= mDragDistThreshold && mTimeFraction >= 1) {
mSysUiProxy.onAssistantGestureCompletion(0);
startAssistantInternal(SWIPE);
@@ -270,8 +275,11 @@
private class AssistantGestureListener extends SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- if (isValidAssistantGestureAngle(velocityX, -velocityY)
- && !mLaunchedAssistant && mState != STATE_DELEGATE_ACTIVE) {
+ if (!mIsAssistGestureConstrained
+ && isValidAssistantGestureAngle(velocityX, -velocityY)
+ && mDistance >= mFlingDistThreshold
+ && !mLaunchedAssistant
+ && mState != STATE_DELEGATE_ACTIVE) {
mLastProgress = 1;
try {
mSysUiProxy.onAssistantGestureCompletion(
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index db2af59..b24c788 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -22,6 +22,8 @@
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.TouchInteractionService.INTENT_EXTRA_LOG_TRACE_ID;
+import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync;
import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
import android.content.ComponentName;
@@ -34,18 +36,16 @@
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
-import android.view.WindowManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.DefaultDisplay;
import com.android.quickstep.LockScreenRecentsActivity;
import com.android.quickstep.MultiStateCallback;
import com.android.quickstep.SwipeSharedState;
import com.android.quickstep.util.ClipAnimationHelper;
import com.android.quickstep.util.RecentsAnimationListenerSet;
import com.android.quickstep.util.SwipeAnimationTargetSet;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.BackgroundExecutor;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -77,6 +77,7 @@
private final PointF mTouchDown = new PointF();
private final ClipAnimationHelper mClipAnimationHelper;
+ private int mLogId;
private final ClipAnimationHelper.TransformParams mTransformParams;
private final Point mDisplaySize;
private final MultiStateCallback mStateCallback;
@@ -91,19 +92,20 @@
private SwipeAnimationTargetSet mTargetSet;
public DeviceLockedInputConsumer(Context context, SwipeSharedState swipeSharedState,
- InputMonitorCompat inputMonitorCompat, RectF swipeTouchRegion, int runningTaskId) {
+ InputMonitorCompat inputMonitorCompat, RectF swipeTouchRegion, int runningTaskId,
+ int logId) {
mContext = context;
mTouchSlopSquared = squaredTouchSlop(context);
mSwipeSharedState = swipeSharedState;
mClipAnimationHelper = new ClipAnimationHelper(context);
+ mLogId = logId;
mTransformParams = new ClipAnimationHelper.TransformParams();
mInputMonitorCompat = inputMonitorCompat;
mSwipeTouchRegion = swipeTouchRegion;
mRunningTaskId = runningTaskId;
// Do not use DeviceProfile as the user data might be locked
- mDisplaySize = new Point();
- context.getSystemService(WindowManager.class).getDefaultDisplay().getRealSize(mDisplaySize);
+ mDisplaySize = DefaultDisplay.INSTANCE.get(context).getInfo().realSize;
// Init states
mStateCallback = new MultiStateCallback(STATE_NAMES);
@@ -206,12 +208,11 @@
Intent intent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_DEFAULT)
.setComponent(new ComponentName(mContext, LockScreenRecentsActivity.class))
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ .putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId);
mInputMonitorCompat.pilferPointers();
- BackgroundExecutor.get().submit(
- () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
- intent, null, newListenerSet, null, null));
+ startRecentsActivityAsync(intent, newListenerSet);
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
index d05ca2a..e0ff8af 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
@@ -15,344 +15,442 @@
*/
package com.android.quickstep.inputconsumers;
-import static android.view.MotionEvent.ACTION_CANCEL;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_MOVE;
-import static android.view.MotionEvent.ACTION_POINTER_DOWN;
-import static android.view.MotionEvent.ACTION_POINTER_UP;
-import static android.view.MotionEvent.ACTION_UP;
-import static android.view.MotionEvent.INVALID_POINTER_ID;
-
-import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION;
+import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID;
+import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL;
import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
-import static com.android.quickstep.WindowTransformSwipeHandler.MIN_SWIPE_DURATION;
-import static com.android.quickstep.inputconsumers.OtherActivityInputConsumer.QUICKSTEP_TOUCH_SLOP_RATIO;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.HOME;
+import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.LAST_TASK;
+import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.NEW_TASK;
+import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.RECENTS;
+import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
+import android.animation.AnimatorSet;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.graphics.PointF;
-import android.graphics.Rect;
import android.graphics.RectF;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.ViewConfiguration;
-import android.view.WindowManager;
+import android.os.Bundle;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
-import com.android.quickstep.ActivityControlHelper;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
+import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.BaseSwipeUpHandler;
+import com.android.quickstep.MultiStateCallback;
import com.android.quickstep.OverviewComponentObserver;
+import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.RecentsModel;
import com.android.quickstep.SwipeSharedState;
-import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
-import com.android.quickstep.util.NavBarPosition;
-import com.android.quickstep.util.RecentsAnimationListenerSet;
+import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.quickstep.util.ObjectWrapper;
+import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.SwipeAnimationTargetSet;
-import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.BackgroundExecutor;
-import com.android.systemui.shared.system.InputMonitorCompat;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.InputConsumerController;
-public class FallbackNoButtonInputConsumer implements InputConsumer, SwipeAnimationListener {
+public class FallbackNoButtonInputConsumer extends
+ BaseSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
- private static final int STATE_NOT_FINISHED = 0;
- private static final int STATE_FINISHED_TO_HOME = 1;
- private static final int STATE_FINISHED_TO_APP = 2;
+ private static final String[] STATE_NAMES = DEBUG_STATES ? new String[5] : null;
- private static final float PROGRESS_TO_END_GESTURE = -2;
+ private static int getFlagForIndex(int index, String name) {
+ if (DEBUG_STATES) {
+ STATE_NAMES[index] = name;
+ }
+ return 1 << index;
+ }
- private final ActivityControlHelper mActivityControlHelper;
- private final InputMonitorCompat mInputMonitor;
- private final Context mContext;
- private final NavBarPosition mNavBarPosition;
- private final SwipeSharedState mSwipeSharedState;
- private final OverviewComponentObserver mOverviewComponentObserver;
- private final int mRunningTaskId;
+ private static final int STATE_RECENTS_PRESENT =
+ getFlagForIndex(0, "STATE_RECENTS_PRESENT");
+ private static final int STATE_HANDLER_INVALIDATED =
+ getFlagForIndex(1, "STATE_HANDLER_INVALIDATED");
- private final ClipAnimationHelper mClipAnimationHelper;
- private final TransformParams mTransformParams = new TransformParams();
- private final float mTransitionDragLength;
- private final DeviceProfile mDP;
+ private static final int STATE_GESTURE_CANCELLED =
+ getFlagForIndex(2, "STATE_GESTURE_CANCELLED");
+ private static final int STATE_GESTURE_COMPLETED =
+ getFlagForIndex(3, "STATE_GESTURE_COMPLETED");
+ private static final int STATE_APP_CONTROLLER_RECEIVED =
+ getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
- private final RectF mSwipeTouchRegion;
- private final boolean mDisableHorizontalSwipe;
+ public enum GestureEndTarget {
+ HOME(3, 100, 1),
+ RECENTS(1, 300, 0),
+ LAST_TASK(0, 150, 1),
+ NEW_TASK(0, 150, 1);
- private final PointF mDownPos = new PointF();
- private final PointF mLastPos = new PointF();
+ private final float mEndProgress;
+ private final long mDurationMultiplier;
+ private final float mLauncherAlpha;
- private int mActivePointerId = -1;
- // Slop used to determine when we say that the gesture has started.
- private boolean mPassedPilferInputSlop;
+ GestureEndTarget(float endProgress, long durationMultiplier, float launcherAlpha) {
+ mEndProgress = endProgress;
+ mDurationMultiplier = durationMultiplier;
+ mLauncherAlpha = launcherAlpha;
+ }
+ }
- private VelocityTracker mVelocityTracker;
+ private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged);
- // Distance after which we start dragging the window.
- private final float mTouchSlop;
+ private boolean mIsMotionPaused = false;
+ private GestureEndTarget mEndTarget;
- // Might be displacement in X or Y, depending on the direction we are swiping from the nav bar.
- private float mStartDisplacement;
- private SwipeAnimationTargetSet mSwipeAnimationTargetSet;
- private float mProgress;
+ private final boolean mInQuickSwitchMode;
+ private final boolean mContinuingLastGesture;
+ private final boolean mRunningOverHome;
+ private final boolean mSwipeUpOverHome;
- private int mState = STATE_NOT_FINISHED;
+ private final RunningTaskInfo mRunningTaskInfo;
+
+ private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f);
+ private RunningWindowAnim mFinishAnimation;
public FallbackNoButtonInputConsumer(Context context,
- ActivityControlHelper activityControlHelper, InputMonitorCompat inputMonitor,
- SwipeSharedState swipeSharedState, RectF swipeTouchRegion,
OverviewComponentObserver overviewComponentObserver,
- boolean disableHorizontalSwipe, RunningTaskInfo runningTaskInfo) {
- mContext = context;
- mActivityControlHelper = activityControlHelper;
- mInputMonitor = inputMonitor;
- mOverviewComponentObserver = overviewComponentObserver;
- mRunningTaskId = runningTaskInfo.id;
+ RunningTaskInfo runningTaskInfo, RecentsModel recentsModel,
+ InputConsumerController inputConsumer,
+ boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
+ super(context, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id);
+ mLauncherAlpha.value = 1;
- mSwipeSharedState = swipeSharedState;
- mSwipeTouchRegion = swipeTouchRegion;
- mDisableHorizontalSwipe = disableHorizontalSwipe;
+ mRunningTaskInfo = runningTaskInfo;
+ mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
+ mContinuingLastGesture = continuingLastGesture;
+ mRunningOverHome = ActivityManagerWrapper.isHomeTask(runningTaskInfo);
+ mSwipeUpOverHome = mRunningOverHome && !mInQuickSwitchMode;
- mNavBarPosition = new NavBarPosition(context);
- mVelocityTracker = VelocityTracker.obtain();
-
- mTouchSlop = QUICKSTEP_TOUCH_SLOP_RATIO
- * ViewConfiguration.get(context).getScaledTouchSlop();
-
- mClipAnimationHelper = new ClipAnimationHelper(context);
-
- mDP = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context).copy(context);
- Rect tempRect = new Rect();
- mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
- mDP, context, tempRect);
- mClipAnimationHelper.updateTargetRect(tempRect);
- }
-
- @Override
- public int getType() {
- return TYPE_FALLBACK_NO_BUTTON;
- }
-
- @Override
- public void onMotionEvent(MotionEvent ev) {
- if (mVelocityTracker == null) {
- return;
- }
-
- mVelocityTracker.addMovement(ev);
- if (ev.getActionMasked() == ACTION_POINTER_UP) {
- mVelocityTracker.clear();
- }
-
- switch (ev.getActionMasked()) {
- case ACTION_DOWN: {
- mActivePointerId = ev.getPointerId(0);
- mDownPos.set(ev.getX(), ev.getY());
- mLastPos.set(mDownPos);
- break;
- }
- case ACTION_POINTER_DOWN: {
- if (!mPassedPilferInputSlop) {
- // Cancel interaction in case of multi-touch interaction
- int ptrIdx = ev.getActionIndex();
- if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) {
- forceCancelGesture(ev);
- }
- }
- break;
- }
- case ACTION_POINTER_UP: {
- int ptrIdx = ev.getActionIndex();
- int ptrId = ev.getPointerId(ptrIdx);
- if (ptrId == mActivePointerId) {
- final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
- mDownPos.set(
- ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
- ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
- mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
- mActivePointerId = ev.getPointerId(newPointerIdx);
- }
- break;
- }
- case ACTION_MOVE: {
- int pointerIndex = ev.findPointerIndex(mActivePointerId);
- if (pointerIndex == INVALID_POINTER_ID) {
- break;
- }
- mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
- float displacement = getDisplacement(ev);
-
- if (!mPassedPilferInputSlop) {
- if (mDisableHorizontalSwipe && Math.abs(mLastPos.x - mDownPos.x)
- > Math.abs(mLastPos.y - mDownPos.y)) {
- // Horizontal gesture is not allowed in this region
- forceCancelGesture(ev);
- break;
- }
-
- if (Math.abs(displacement) >= mTouchSlop) {
- mPassedPilferInputSlop = true;
-
- // Deferred gesture, start the animation and gesture tracking once
- // we pass the actual touch slop
- startTouchTrackingForWindowAnimation(displacement);
- }
- } else {
- updateDisplacement(displacement - mStartDisplacement);
- }
- break;
- }
- case ACTION_CANCEL:
- case ACTION_UP: {
- finishTouchTracking(ev);
- break;
- }
- }
- }
-
- private void startTouchTrackingForWindowAnimation(float displacement) {
- mStartDisplacement = Math.min(displacement, -mTouchSlop);
-
- RecentsAnimationListenerSet listenerSet =
- mSwipeSharedState.newRecentsAnimationListenerSet();
- listenerSet.addListener(this);
- Intent homeIntent = mOverviewComponentObserver.getHomeIntent();
- BackgroundExecutor.get().submit(
- () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
- homeIntent, null, listenerSet, null, null));
-
- ActivityManagerWrapper.getInstance().closeSystemWindows(
- CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
- mInputMonitor.pilferPointers();
- }
-
- private void updateDisplacement(float displacement) {
- mProgress = displacement / mTransitionDragLength;
- mTransformParams.setProgress(mProgress);
-
- if (mSwipeAnimationTargetSet != null) {
- mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
- }
- }
-
- private void forceCancelGesture(MotionEvent ev) {
- int action = ev.getAction();
- ev.setAction(ACTION_CANCEL);
- finishTouchTracking(ev);
- ev.setAction(action);
- }
-
- /**
- * Called when the gesture has ended. Does not correlate to the completion of the interaction as
- * the animation can still be running.
- */
- private void finishTouchTracking(MotionEvent ev) {
- if (ev.getAction() == ACTION_CANCEL) {
- mState = STATE_FINISHED_TO_APP;
+ if (mSwipeUpOverHome) {
+ mClipAnimationHelper.setBaseAlphaCallback((t, a) -> 1 - mLauncherAlpha.value);
} else {
- mVelocityTracker.computeCurrentVelocity(1000,
- ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
- float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
- float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
- float velocity = mNavBarPosition.isRightEdge() ? velocityX
- : mNavBarPosition.isLeftEdge() ? -velocityX
- : velocityY;
+ mClipAnimationHelper.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value);
+ }
+
+ initStateCallbacks();
+ }
+
+ private void initStateCallbacks() {
+ mStateCallback = new MultiStateCallback(STATE_NAMES);
+
+ mStateCallback.addCallback(STATE_HANDLER_INVALIDATED,
+ this::onHandlerInvalidated);
+ mStateCallback.addCallback(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED,
+ this::onHandlerInvalidatedWithRecents);
+
+ mStateCallback.addCallback(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED,
+ this::finishAnimationTargetSetAnimationComplete);
+
+ if (mInQuickSwitchMode) {
+ mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED
+ | STATE_RECENTS_PRESENT,
+ this::finishAnimationTargetSet);
+ } else {
+ mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED,
+ this::finishAnimationTargetSet);
+ }
+ }
+
+ private void onLauncherAlphaChanged() {
+ if (mRecentsAnimationWrapper.targetSet != null && mEndTarget == null) {
+ applyTransformUnchecked();
+ }
+ }
+
+ @Override
+ protected boolean onActivityInit(final RecentsActivity activity, Boolean alreadyOnHome) {
+ mActivity = activity;
+ mRecentsView = activity.getOverviewPanel();
+ linkRecentsViewScroll();
+ mRecentsView.setDisallowScrollToClearAll(true);
+ mRecentsView.getClearAllButton().setVisibilityAlpha(0);
+
+ mRecentsView.setZoomProgress(1);
+
+ if (!mContinuingLastGesture) {
+ if (mRunningOverHome) {
+ mRecentsView.onGestureAnimationStart(mRunningTaskInfo);
+ } else {
+ mRecentsView.onGestureAnimationStart(mRunningTaskId);
+ }
+ }
+ setStateOnUiThread(STATE_RECENTS_PRESENT);
+ return true;
+ }
+
+ @Override
+ protected boolean moveWindowWithRecentsScroll() {
+ return mInQuickSwitchMode;
+ }
+
+ @Override
+ public void initWhenReady() {
+ if (mInQuickSwitchMode) {
+ // Only init if we are in quickswitch mode
+ super.initWhenReady();
+ }
+ }
+
+ @Override
+ public void updateDisplacement(float displacement) {
+ if (!mInQuickSwitchMode) {
+ super.updateDisplacement(displacement);
+ }
+ }
+
+ @Override
+ protected InputConsumer createNewInputProxyHandler() {
+ // Just consume all input on the active task
+ return InputConsumer.NO_OP;
+ }
+
+ @Override
+ public void onMotionPauseChanged(boolean isPaused) {
+ if (!mInQuickSwitchMode) {
+ mIsMotionPaused = isPaused;
+ mLauncherAlpha.animateToValue(mLauncherAlpha.value, isPaused ? 0 : 1)
+ .setDuration(150).start();
+ performHapticFeedback();
+ }
+ }
+
+ @Override
+ public Intent getLaunchIntent() {
+ if (mInQuickSwitchMode || mSwipeUpOverHome) {
+ return mOverviewComponentObserver.getOverviewIntent();
+ } else {
+ return mOverviewComponentObserver.getHomeIntent();
+ }
+ }
+
+ @Override
+ public void updateFinalShift() {
+ mTransformParams.setProgress(mCurrentShift.value);
+ mRecentsAnimationWrapper.setWindowThresholdCrossed(!mInQuickSwitchMode
+ && (mCurrentShift.value > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD));
+ if (mRecentsAnimationWrapper.targetSet != null) {
+ applyTransformUnchecked();
+ }
+ }
+
+ @Override
+ public void onGestureCancelled() {
+ updateDisplacement(0);
+ mEndTarget = LAST_TASK;
+ setStateOnUiThread(STATE_GESTURE_CANCELLED);
+ }
+
+ @Override
+ public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
+ mEndVelocityPxPerMs.set(0, velocity.y / 1000);
+ if (mInQuickSwitchMode) {
+ // For now set it to non-null, it will be reset before starting the animation
+ mEndTarget = LAST_TASK;
+ } else {
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
- boolean isFling = Math.abs(velocity) > flingThreshold;
+ boolean isFling = Math.abs(endVelocity) > flingThreshold;
- boolean goingHome;
- if (!isFling) {
- goingHome = -mProgress >= MIN_PROGRESS_FOR_OVERVIEW;
+ if (isFling) {
+ mEndTarget = endVelocity < 0 ? HOME : LAST_TASK;
+ } else if (mIsMotionPaused) {
+ mEndTarget = RECENTS;
} else {
- goingHome = velocity < 0;
+ mEndTarget = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? HOME : LAST_TASK;
+ }
+ }
+ setStateOnUiThread(STATE_GESTURE_COMPLETED);
+ }
+
+ @Override
+ public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) {
+ if (mInQuickSwitchMode && mEndTarget != null) {
+ sharedState.canGestureBeContinued = true;
+ sharedState.goingToLauncher = false;
+
+ mCanceled = true;
+ mCurrentShift.cancelAnimation();
+ if (mFinishAnimation != null) {
+ mFinishAnimation.cancel();
}
- if (goingHome) {
- mState = STATE_FINISHED_TO_HOME;
- } else {
- mState = STATE_FINISHED_TO_APP;
+ if (mRecentsView != null) {
+ if (mFinishingRecentsAnimationForNewTaskId != -1) {
+ TaskView newRunningTaskView = mRecentsView.getTaskView(
+ mFinishingRecentsAnimationForNewTaskId);
+ int newRunningTaskId = newRunningTaskView != null
+ ? newRunningTaskView.getTask().key.id
+ : -1;
+ mRecentsView.setCurrentTask(newRunningTaskId);
+ sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId);
+ }
+ mRecentsView.setOnScrollChangeListener(null);
+ }
+ } else {
+ setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+ }
+ }
+
+ private void onHandlerInvalidated() {
+ mActivityInitListener.unregister();
+ if (mGestureEndCallback != null) {
+ mGestureEndCallback.run();
+ }
+ if (mFinishAnimation != null) {
+ mFinishAnimation.end();
+ }
+ }
+
+ private void onHandlerInvalidatedWithRecents() {
+ mRecentsView.onGestureAnimationEnd();
+ mRecentsView.setDisallowScrollToClearAll(false);
+ mRecentsView.getClearAllButton().setVisibilityAlpha(1);
+ }
+
+ private void finishAnimationTargetSetAnimationComplete() {
+ switch (mEndTarget) {
+ case HOME: {
+ if (mSwipeUpOverHome) {
+ mRecentsAnimationWrapper.finish(false, null, false);
+ // Send a home intent to clear the task stack
+ mContext.startActivity(mOverviewComponentObserver.getHomeIntent());
+ } else {
+ mRecentsAnimationWrapper.finish(true, null, true);
+ }
+ break;
+ }
+ case LAST_TASK:
+ mRecentsAnimationWrapper.finish(false, null, false);
+ break;
+ case RECENTS: {
+ if (mSwipeUpOverHome) {
+ mRecentsAnimationWrapper.finish(true, null, true);
+ break;
+ }
+
+ ThumbnailData thumbnail =
+ mRecentsAnimationWrapper.targetSet.controller.screenshotTask(mRunningTaskId);
+ mRecentsAnimationWrapper.setDeferCancelUntilNextTransition(true /* defer */,
+ false /* screenshot */);
+
+ ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
+ ActivityOptionsCompat.setFreezeRecentTasksList(options);
+
+ Bundle extras = new Bundle();
+ extras.putBinder(EXTRA_THUMBNAIL, new ObjectWrapper<>(thumbnail));
+ extras.putInt(EXTRA_TASK_ID, mRunningTaskId);
+
+ Intent intent = new Intent(mOverviewComponentObserver.getOverviewIntent())
+ .putExtras(extras);
+ mContext.startActivity(intent, options.toBundle());
+ mRecentsAnimationWrapper.targetSet.controller.cleanupScreenshot();
+ break;
+ }
+ case NEW_TASK: {
+ startNewTask(STATE_HANDLER_INVALIDATED, b -> {});
+ break;
}
}
- if (mSwipeAnimationTargetSet != null) {
- finishAnimationTargetSet();
- }
+ setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
private void finishAnimationTargetSet() {
- if (mState == STATE_FINISHED_TO_APP) {
- mSwipeAnimationTargetSet.finishController(false, null, false);
- } else {
- if (mProgress < PROGRESS_TO_END_GESTURE) {
- mSwipeAnimationTargetSet.finishController(true, null, true);
+ if (mInQuickSwitchMode) {
+ // Recalculate the end target, some views might have been initialized after
+ // gesture has ended.
+ if (mRecentsView == null || !mRecentsAnimationWrapper.hasTargets()) {
+ mEndTarget = LAST_TASK;
} else {
- long duration = (long) (Math.min(mProgress - PROGRESS_TO_END_GESTURE, 1)
- * MAX_SWIPE_DURATION / Math.abs(PROGRESS_TO_END_GESTURE));
- if (duration < 0) {
- duration = MIN_SWIPE_DURATION;
- }
-
- ValueAnimator anim = ValueAnimator.ofFloat(mProgress, PROGRESS_TO_END_GESTURE);
- anim.addUpdateListener(a -> {
- float p = (Float) anim.getAnimatedValue();
- mTransformParams.setProgress(p);
- mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
- });
- anim.setDuration(duration);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mSwipeAnimationTargetSet.finishController(true, null, true);
- }
- });
- anim.start();
+ final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
+ final int taskToLaunch = mRecentsView.getNextPage();
+ mEndTarget = (runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex)
+ ? NEW_TASK : LAST_TASK;
}
}
+
+ float endProgress = mEndTarget.mEndProgress;
+ long duration = (long) (mEndTarget.mDurationMultiplier *
+ Math.abs(endProgress - mCurrentShift.value));
+ if (mRecentsView != null) {
+ duration = Math.max(duration, mRecentsView.getScroller().getDuration());
+ }
+ if (mCurrentShift.value != endProgress || mInQuickSwitchMode) {
+ AnimationSuccessListener endListener = new AnimationSuccessListener() {
+
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ finishAnimationTargetSetAnimationComplete();
+ mFinishAnimation = null;
+ }
+ };
+
+ if (mEndTarget == HOME && !mRunningOverHome) {
+ RectFSpringAnim anim = createWindowAnimationToHome(mCurrentShift.value, duration);
+ anim.addAnimatorListener(endListener);
+ anim.start(mEndVelocityPxPerMs);
+ mFinishAnimation = RunningWindowAnim.wrap(anim);
+ } else {
+
+ AnimatorSet anim = new AnimatorSet();
+ anim.play(mLauncherAlpha.animateToValue(
+ mLauncherAlpha.value, mEndTarget.mLauncherAlpha));
+ anim.play(mCurrentShift.animateToValue(mCurrentShift.value, endProgress));
+
+ anim.setDuration(duration);
+ anim.addListener(endListener);
+ anim.start();
+ mFinishAnimation = RunningWindowAnim.wrap(anim);
+ }
+
+ } else {
+ finishAnimationTargetSetAnimationComplete();
+ }
}
@Override
public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
- mSwipeAnimationTargetSet = targetSet;
- Rect overviewStackBounds = new Rect(0, 0, mDP.widthPx, mDP.heightPx);
- RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
+ super.onRecentsAnimationStart(targetSet);
+ mRecentsAnimationWrapper.enableInputConsumer();
- mDP.updateIsSeascape(mContext.getSystemService(WindowManager.class));
- if (runningTaskTarget != null) {
- mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
+ if (mRunningOverHome) {
+ mClipAnimationHelper.prepareAnimation(mDp, true);
}
- mClipAnimationHelper.prepareAnimation(mDP, false /* isOpening */);
+ applyTransformUnchecked();
- overviewStackBounds
- .inset(-overviewStackBounds.width() / 5, -overviewStackBounds.height() / 5);
- mClipAnimationHelper.updateTargetRect(overviewStackBounds);
- mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
-
- if (mState != STATE_NOT_FINISHED) {
- finishAnimationTargetSet();
- }
+ setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
}
@Override
- public void onRecentsAnimationCanceled() { }
-
- private float getDisplacement(MotionEvent ev) {
- if (mNavBarPosition.isRightEdge()) {
- return ev.getX() - mDownPos.x;
- } else if (mNavBarPosition.isLeftEdge()) {
- return mDownPos.x - ev.getX();
- } else {
- return ev.getY() - mDownPos.y;
- }
+ public void onRecentsAnimationCanceled() {
+ mRecentsAnimationWrapper.setController(null);
+ setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
- @Override
- public boolean allowInterceptByParent() {
- return !mPassedPilferInputSlop;
+ /**
+ * Creates an animation that transforms the current app window into the home app.
+ * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
+ */
+ private RectFSpringAnim createWindowAnimationToHome(float startProgress, long duration) {
+ HomeAnimationFactory factory = new HomeAnimationFactory() {
+ @Override
+ public RectF getWindowTargetRect() {
+ return HomeAnimationFactory.getDefaultWindowTargetRect(mDp);
+ }
+
+ @Override
+ public AnimatorPlaybackController createActivityAnimationToHome() {
+ AnimatorSet anim = new AnimatorSet();
+ anim.play(mLauncherAlpha.animateToValue(mLauncherAlpha.value, 1));
+ anim.setDuration(duration);
+ return AnimatorPlaybackController.wrap(anim, duration);
+ }
+ };
+ return createWindowAnimationToHome(startProgress, factory);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
index f5cf654..a1e5d47 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
@@ -33,7 +33,6 @@
int TYPE_SCREEN_PINNED = 1 << 6;
int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7;
int TYPE_RESET_GESTURE = 1 << 8;
- int TYPE_FALLBACK_NO_BUTTON = 1 << 9;
String[] NAMES = new String[] {
"TYPE_NO_OP", // 0
@@ -45,7 +44,6 @@
"TYPE_SCREEN_PINNED", // 6
"TYPE_OVERVIEW_WITHOUT_FOCUS", // 7
"TYPE_RESET_GESTURE", // 8
- "TYPE_FALLBACK_NO_BUTTON", // 9
};
InputConsumer NO_OP = () -> TYPE_NO_OP;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 4c137d3..e41880d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -22,12 +22,13 @@
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.INVALID_POINTER_ID;
-
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.util.RaceConditionTracker.ENTER;
import static com.android.launcher3.util.RaceConditionTracker.EXIT;
+import static com.android.quickstep.TouchInteractionService.INTENT_EXTRA_LOG_TRACE_ID;
import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
+import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.annotation.TargetApi;
@@ -43,32 +44,25 @@
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
-
+import androidx.annotation.UiThread;
import com.android.launcher3.R;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RaceConditionTracker;
import com.android.launcher3.util.TraceHelper;
-import com.android.quickstep.ActivityControlHelper;
+import com.android.quickstep.BaseSwipeUpHandler;
+import com.android.quickstep.BaseSwipeUpHandler.Factory;
import com.android.quickstep.OverviewCallbacks;
-import com.android.quickstep.RecentsModel;
import com.android.quickstep.SwipeSharedState;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.WindowTransformSwipeHandler;
-import com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget;
import com.android.quickstep.util.CachedEventDispatcher;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.NavBarPosition;
import com.android.quickstep.util.RecentsAnimationListenerSet;
import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.BackgroundExecutor;
-import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.InputMonitorCompat;
-
import java.util.function.Consumer;
-import androidx.annotation.UiThread;
-
/**
* Input consumer for handling events originating from an activity other than Launcher
*/
@@ -83,16 +77,14 @@
private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
private final RunningTaskInfo mRunningTask;
- private final RecentsModel mRecentsModel;
- private final Intent mHomeIntent;
- private final ActivityControlHelper mActivityControlHelper;
private final OverviewCallbacks mOverviewCallbacks;
- private final InputConsumerController mInputConsumer;
private final SwipeSharedState mSwipeSharedState;
private final InputMonitorCompat mInputMonitorCompat;
private final SysUINavigationMode.Mode mMode;
private final RectF mSwipeTouchRegion;
+ private final BaseSwipeUpHandler.Factory mHandlerFactory;
+
private final NavBarPosition mNavBarPosition;
private final Consumer<OtherActivityInputConsumer> mOnCompleteCallback;
@@ -100,7 +92,7 @@
private final float mMotionPauseMinDisplacement;
private VelocityTracker mVelocityTracker;
- private WindowTransformSwipeHandler mInteractionHandler;
+ private BaseSwipeUpHandler mInteractionHandler;
private final boolean mIsDeferredDownTarget;
private final PointF mDownPos = new PointF();
@@ -114,7 +106,7 @@
private final boolean mDisableHorizontalSwipe;
// Slop used to check when we start moving window.
- private boolean mPaddedWindowMoveSlop;
+ private boolean mPassedWindowMoveSlop;
// Slop used to determine when we say that the gesture has started.
private boolean mPassedPilferInputSlop;
@@ -126,22 +118,22 @@
ActivityManagerWrapper.getInstance().cancelRecentsAnimation(
true /* restoreHomeStackPosition */);
};
+ private int mLogId;
public OtherActivityInputConsumer(Context base, RunningTaskInfo runningTaskInfo,
- RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks,
- InputConsumerController inputConsumer,
Consumer<OtherActivityInputConsumer> onCompleteCallback,
SwipeSharedState swipeSharedState, InputMonitorCompat inputMonitorCompat,
- RectF swipeTouchRegion, boolean disableHorizontalSwipe) {
+ RectF swipeTouchRegion, boolean disableHorizontalSwipe,
+ Factory handlerFactory, int logId) {
super(base);
+ mLogId = logId;
mMainThreadHandler = new Handler(Looper.getMainLooper());
mRunningTask = runningTaskInfo;
- mRecentsModel = recentsModel;
- mHomeIntent = homeIntent;
mMode = SysUINavigationMode.getMode(base);
mSwipeTouchRegion = swipeTouchRegion;
+ mHandlerFactory = handlerFactory;
mMotionPauseDetector = new MotionPauseDetector(base);
mMotionPauseMinDisplacement = base.getResources().getDimension(
@@ -150,11 +142,9 @@
mVelocityTracker = VelocityTracker.obtain();
mInputMonitorCompat = inputMonitorCompat;
- mActivityControlHelper = activityControl;
boolean continuingPreviousGesture = swipeSharedState.getActiveListener() != null;
mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
mOverviewCallbacks = overviewCallbacks;
- mInputConsumer = inputConsumer;
mSwipeSharedState = swipeSharedState;
mNavBarPosition = new NavBarPosition(base);
@@ -163,7 +153,7 @@
float slop = QUICKSTEP_TOUCH_SLOP_RATIO * mTouchSlop;
mSquaredTouchSlop = slop * slop;
- mPassedPilferInputSlop = mPaddedWindowMoveSlop = continuingPreviousGesture;
+ mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture;
mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe;
}
@@ -186,7 +176,7 @@
}
// Proxy events to recents view
- if (mPaddedWindowMoveSlop && mInteractionHandler != null
+ if (mPassedWindowMoveSlop && mInteractionHandler != null
&& !mRecentsViewDispatcher.hasConsumer()) {
mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher(
mNavBarPosition.getRotationMode()));
@@ -213,7 +203,7 @@
// Start the window animation on down to give more time for launcher to draw if the
// user didn't start the gesture over the back button
if (!mIsDeferredDownTarget) {
- startTouchTrackingForWindowAnimation(ev.getEventTime());
+ startTouchTrackingForWindowAnimation(ev.getEventTime(), false);
}
RaceConditionTracker.onEvent(DOWN_EVT, EXIT);
@@ -251,17 +241,21 @@
float displacement = getDisplacement(ev);
float displacementX = mLastPos.x - mDownPos.x;
- if (!mPaddedWindowMoveSlop) {
+ if (!mPassedWindowMoveSlop) {
if (!mIsDeferredDownTarget) {
// Normal gesture, ensure we pass the drag slop before we start tracking
// the gesture
if (Math.abs(displacement) > mTouchSlop) {
- mPaddedWindowMoveSlop = true;
+ mPassedWindowMoveSlop = true;
mStartDisplacement = Math.min(displacement, -mTouchSlop);
}
}
}
+ float horizontalDist = Math.abs(displacementX);
+ float upDist = -displacement;
+ boolean isLikelyToStartNewTask = horizontalDist > upDist;
+
if (!mPassedPilferInputSlop) {
float displacementY = mLastPos.y - mDownPos.y;
if (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop) {
@@ -277,10 +271,11 @@
if (mIsDeferredDownTarget) {
// Deferred gesture, start the animation and gesture tracking once
// we pass the actual touch slop
- startTouchTrackingForWindowAnimation(ev.getEventTime());
+ startTouchTrackingForWindowAnimation(
+ ev.getEventTime(), isLikelyToStartNewTask);
}
- if (!mPaddedWindowMoveSlop) {
- mPaddedWindowMoveSlop = true;
+ if (!mPassedWindowMoveSlop) {
+ mPassedWindowMoveSlop = true;
mStartDisplacement = Math.min(displacement, -mTouchSlop);
}
@@ -289,15 +284,12 @@
}
if (mInteractionHandler != null) {
- if (mPaddedWindowMoveSlop) {
+ if (mPassedWindowMoveSlop) {
// Move
mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
}
if (mMode == Mode.NO_BUTTON) {
- float horizontalDist = Math.abs(displacementX);
- float upDist = -displacement;
- boolean isLikelyToStartNewTask = horizontalDist > upDist;
mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement
|| isLikelyToStartNewTask);
mMotionPauseDetector.addPosition(displacement, ev.getEventTime());
@@ -329,16 +321,14 @@
mInteractionHandler.onGestureStarted();
}
- private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
+ private void startTouchTrackingForWindowAnimation(
+ long touchTimeMs, boolean isLikelyToStartNewTask) {
TOUCH_INTERACTION_LOG.addLog("startRecentsAnimation");
RecentsAnimationListenerSet listenerSet = mSwipeSharedState.getActiveListener();
- final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
- mRunningTask, this, touchTimeMs, mActivityControlHelper,
- listenerSet != null, mInputConsumer);
+ final BaseSwipeUpHandler handler = mHandlerFactory.newHandler(mRunningTask, touchTimeMs,
+ listenerSet != null, isLikelyToStartNewTask);
- // Preload the plan
- mRecentsModel.getTasks(null);
mInteractionHandler = handler;
handler.setGestureEndCallback(this::onInteractionGestureFinished);
mMotionPauseDetector.setOnMotionPauseListener(handler::onMotionPauseChanged);
@@ -352,9 +342,9 @@
RecentsAnimationListenerSet newListenerSet =
mSwipeSharedState.newRecentsAnimationListenerSet();
newListenerSet.addListener(handler);
- BackgroundExecutor.get().submit(
- () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
- mHomeIntent, null, newListenerSet, null, null));
+ Intent intent = handler.getLaunchIntent();
+ intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId);
+ startRecentsActivityAsync(intent, newListenerSet);
}
}
@@ -366,7 +356,7 @@
RaceConditionTracker.onEvent(UP_EVT, ENTER);
TraceHelper.endSection("TouchInt");
- if (mPaddedWindowMoveSlop && mInteractionHandler != null) {
+ if (mPassedWindowMoveSlop && mInteractionHandler != null) {
if (ev.getActionMasked() == ACTION_CANCEL) {
mInteractionHandler.onGestureCancelled();
} else {
@@ -409,14 +399,7 @@
// The consumer is being switched while we are active. Set up the shared state to be
// used by the next animation
removeListener();
- GestureEndTarget endTarget = mInteractionHandler.getGestureEndTarget();
- mSwipeSharedState.canGestureBeContinued = endTarget != null && endTarget.canBeContinued;
- mSwipeSharedState.goingToLauncher = endTarget != null && endTarget.isLauncher;
- if (mSwipeSharedState.canGestureBeContinued) {
- mInteractionHandler.cancelCurrentAnimation(mSwipeSharedState);
- } else {
- mInteractionHandler.reset();
- }
+ mInteractionHandler.onConsumerAboutToBeSwitched(mSwipeSharedState);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index b021df8..581ab6d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -19,6 +19,7 @@
import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -26,6 +27,7 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.OverviewCallbacks;
import com.android.systemui.shared.system.ActivityManagerWrapper;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 425b8b6..f40d552 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -22,18 +22,21 @@
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.content.Context;
+import android.content.Intent;
import android.graphics.PointF;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.Utilities;
-import com.android.quickstep.OverviewCallbacks;
+import com.android.launcher3.logging.StatsLogUtils;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.quickstep.util.NavBarPosition;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputMonitorCompat;
public class OverviewWithoutFocusInputConsumer implements InputConsumer {
@@ -131,19 +134,28 @@
? -velocityX : (mNavBarPosition.isLeftEdge() ? velocityX : -velocityY);
final boolean triggerQuickstep;
+ int touch = Touch.FLING;
if (Math.abs(velocity) >= ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) {
triggerQuickstep = velocity > 0;
} else {
float displacementX = mDisableHorizontalSwipe ? 0 : (ev.getX() - mDownPos.x);
float displacementY = ev.getY() - mDownPos.y;
triggerQuickstep = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop;
+ touch = Touch.SWIPE;
}
if (triggerQuickstep) {
- OverviewCallbacks.get(mContext).closeAllWindows();
- ActivityManagerWrapper.getInstance()
- .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ mContext.startActivity(new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
TOUCH_INTERACTION_LOG.addLog("startQuickstep");
+ BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
+ int pageIndex = -1; // This number doesn't reflect workspace page index.
+ // It only indicates that launcher client screen was shown.
+ int containerType = StatsLogUtils.getContainerTypeFromState(activity.getCurrentState());
+ activity.getUserEventDispatcher().logActionOnContainer(
+ touch, Direction.UP, containerType, pageIndex);
+ activity.getUserEventDispatcher().setPreviousHomeGesture(true);
} else {
// ignore
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
new file mode 100644
index 0000000..b251f9e
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
@@ -0,0 +1,47 @@
+/*
+ * 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.quickstep.logging;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.appprediction.PredictionUiStateManager;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
+/**
+ * This class handles AOSP MetricsLogger function calls and logging around
+ * quickstep interactions and app launches.
+ */
+@SuppressWarnings("unused")
+public class UserEventDispatcherAppPredictionExtension extends UserEventDispatcherExtension {
+
+ public static final int ALL_APPS_PREDICTION_TIPS = 2;
+
+ private static final String TAG = "UserEventDispatcher";
+
+ public UserEventDispatcherAppPredictionExtension(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onFillInLogContainerData(
+ @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target,
+ @NonNull LauncherLogProto.Target targetParent) {
+ PredictionUiStateManager.fillInPredictedRank(itemInfo, target);
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
index 6dc672e..68007da 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -37,6 +37,7 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.views.RecentsView;
@@ -50,8 +51,6 @@
import com.android.systemui.shared.system.TransactionCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
-import java.util.function.BiFunction;
-
/**
* Utility class to handle window clip animation
*/
@@ -99,8 +98,8 @@
// Whether to boost the opening animation target layers, or the closing
private int mBoostModeTargetLayers = -1;
- private BiFunction<RemoteAnimationTargetCompat, Float, Float> mTaskAlphaCallback =
- (t, a1) -> a1;
+ private TargetAlphaProvider mTaskAlphaCallback = (t, a) -> a;
+ private TargetAlphaProvider mBaseAlphaCallback = (t, a) -> 1;
public ClipAnimationHelper(Context context) {
mWindowCornerRadius = getWindowCornerRadius(context.getResources());
@@ -119,8 +118,12 @@
}
public void updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target) {
- mHomeStackBounds.set(homeStackBounds);
updateSourceStack(target);
+ updateHomeBounds(homeStackBounds);
+ }
+
+ public void updateHomeBounds(Rect homeStackBounds) {
+ mHomeStackBounds.set(homeStackBounds);
}
public void updateTargetRect(Rect targetRect) {
@@ -187,12 +190,12 @@
Rect crop = mTmpRect;
crop.set(app.sourceContainerBounds);
crop.offsetTo(0, 0);
- float alpha = 1f;
+ float alpha;
int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers);
float cornerRadius = 0f;
float scale = Math.max(params.currentRect.width(), mTargetRect.width()) / crop.width();
if (app.mode == targetSet.targetMode) {
- alpha = mTaskAlphaCallback.apply(app, params.targetAlpha);
+ alpha = mTaskAlphaCallback.getAlpha(app, params.targetAlpha);
if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
mTmpMatrix.setRectToRect(mSourceRect, params.currentRect, ScaleToFit.FILL);
mTmpMatrix.postTranslate(app.position.x, app.position.y);
@@ -209,14 +212,22 @@
}
mCurrentCornerRadius = cornerRadius;
}
+ // Fade out Assistant overlay.
+ if (app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT
+ && app.isNotInRecents) {
+ alpha = 1 - Interpolators.DEACCEL_2_5.getInterpolation(progress);
+ }
} else if (targetSet.hasRecents) {
// If home has a different target then recents, reverse anim the
// home target.
alpha = 1 - (progress * params.targetAlpha);
}
- } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && launcherOnTop) {
- crop = null;
- layer = Integer.MAX_VALUE;
+ } else {
+ alpha = mBaseAlphaCallback.getAlpha(app, progress);
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get() && launcherOnTop) {
+ crop = null;
+ layer = Integer.MAX_VALUE;
+ }
}
// Since radius is in Surface space, but we draw the rounded corners in screen space, we
@@ -247,11 +258,14 @@
}
}
- public void setTaskAlphaCallback(
- BiFunction<RemoteAnimationTargetCompat, Float, Float> callback) {
+ public void setTaskAlphaCallback(TargetAlphaProvider callback) {
mTaskAlphaCallback = callback;
}
+ public void setBaseAlphaCallback(TargetAlphaProvider callback) {
+ mBaseAlphaCallback = callback;
+ }
+
public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv) {
fromTaskThumbnailView(ttv, rv, null);
}
@@ -357,6 +371,10 @@
return mCurrentCornerRadius;
}
+ public interface TargetAlphaProvider {
+ float getAlpha(RemoteAnimationTargetCompat target, float expectedAlpha);
+ }
+
public static class TransformParams {
float progress;
public float offsetX;
@@ -384,6 +402,10 @@
return this;
}
+ public float getProgress() {
+ return progress;
+ }
+
public TransformParams setCornerRadius(float cornerRadius) {
this.cornerRadius = cornerRadius;
return this;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java
index 3ce341d..bbb318a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java
@@ -21,9 +21,9 @@
import android.content.Context;
import android.view.Surface;
-import android.view.WindowManager;
import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.util.DefaultDisplay;
import com.android.quickstep.SysUINavigationMode;
/**
@@ -36,8 +36,7 @@
public NavBarPosition(Context context) {
mMode = SysUINavigationMode.getMode(context);
- mDisplayRotation = context.getSystemService(WindowManager.class)
- .getDefaultDisplay().getRotation();
+ mDisplayRotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation;
}
public boolean isRightEdge() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ObjectWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ObjectWrapper.java
new file mode 100644
index 0000000..abfe3ad
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ObjectWrapper.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 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.quickstep.util;
+
+import android.os.Binder;
+import android.os.IBinder;
+
+/**
+ * Utility class to pass non-parcealable objects within same process using parcealable payload.
+ *
+ * It wraps the object in a binder as binders are singleton within a process
+ */
+public class ObjectWrapper<T> extends Binder {
+
+ private final T mObject;
+
+ public ObjectWrapper(T object) {
+ mObject = object;
+ }
+
+ public T get() {
+ return mObject;
+ }
+
+ public static IBinder wrap(Object obj) {
+ return new ObjectWrapper<>(obj);
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
index 83601e6..b1999d7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
@@ -15,14 +15,17 @@
*/
package com.android.quickstep.util;
-import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.graphics.Rect;
import android.util.ArraySet;
+import androidx.annotation.UiThread;
+
import com.android.launcher3.Utilities;
import com.android.launcher3.util.Preconditions;
import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
+import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import com.android.systemui.shared.system.RecentsAnimationListener;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -30,8 +33,6 @@
import java.util.Set;
import java.util.function.Consumer;
-import androidx.annotation.UiThread;
-
/**
* Wrapper around {@link RecentsAnimationListener} which delegates callbacks to multiple listeners
* on the main thread
@@ -39,7 +40,7 @@
public class RecentsAnimationListenerSet implements RecentsAnimationListener {
// The actual app surface is replaced by a screenshot upon recents animation cancelation when
- // deferredWithScreenshot is true. Launcher takes the responsibility to clean up this screenshot
+ // the thumbnailData exists. Launcher takes the responsibility to clean up this screenshot
// after app transition is finished. This delay is introduced to cover the app transition
// period of time.
private final int TRANSITION_DELAY = 100;
@@ -81,7 +82,7 @@
if (mCancelled) {
targetSet.cancelAnimation();
} else {
- Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> {
+ Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
for (SwipeAnimationListener listener : getListeners()) {
listener.onRecentsAnimationStart(targetSet);
}
@@ -90,15 +91,15 @@
}
@Override
- public final void onAnimationCanceled(boolean deferredWithScreenshot) {
- Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> {
+ public final void onAnimationCanceled(ThumbnailData thumbnailData) {
+ Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
for (SwipeAnimationListener listener : getListeners()) {
listener.onRecentsAnimationCanceled();
}
});
// TODO: handle the transition better instead of simply using a transition delay.
- if (deferredWithScreenshot) {
- MAIN_THREAD_EXECUTOR.getHandler().postDelayed(() -> mController.cleanupScreenshot(),
+ if (thumbnailData != null) {
+ MAIN_EXECUTOR.getHandler().postDelayed(() -> mController.cleanupScreenshot(),
TRANSITION_DELAY);
}
}
@@ -109,6 +110,6 @@
public void cancelListener() {
mCancelled = true;
- onAnimationCanceled(false);
+ onAnimationCanceled(null);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
index 77dc6f3..c6eafe6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -225,7 +225,18 @@
}
}
+ public void cancel() {
+ if (mAnimsStarted) {
+ for (OnUpdateListener onUpdateListener : mOnUpdateListeners) {
+ onUpdateListener.onCancel();
+ }
+ }
+ end();
+ }
+
public interface OnUpdateListener {
void onUpdate(RectF currentRect, float progress);
+
+ default void onCancel() { }
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
new file mode 100644
index 0000000..83bc416
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2019 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.quickstep.util;
+
+import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_SHELF_ANIM;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.uioverrides.states.OverviewState;
+
+/**
+ * Animates the shelf between states HIDE, PEEK, and OVERVIEW.
+ */
+
+public class ShelfPeekAnim {
+
+ public static final Interpolator INTERPOLATOR = OVERSHOOT_1_2;
+ public static final long DURATION = 240;
+
+ private final Launcher mLauncher;
+
+ private ShelfAnimState mShelfState;
+ private boolean mIsPeeking;
+
+ public ShelfPeekAnim(Launcher launcher) {
+ mLauncher = launcher;
+ }
+
+ /**
+ * Animates to the given state, canceling the previous animation if it was still running.
+ */
+ public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) {
+ if (mShelfState == shelfState) {
+ return;
+ }
+ mLauncher.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM);
+ mShelfState = shelfState;
+ mIsPeeking = mShelfState == ShelfAnimState.PEEK || mShelfState == ShelfAnimState.HIDE;
+ if (mShelfState == ShelfAnimState.CANCEL) {
+ return;
+ }
+ float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(mLauncher);
+ float shelfOverviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
+ // Peek based on default overview progress so we can see hotseat if we're showing
+ // that instead of predictions in overview.
+ float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(mLauncher);
+ float shelfPeekingProgress = shelfHiddenProgress
+ - (shelfHiddenProgress - defaultOverviewProgress) * 0.25f;
+ float toProgress = mShelfState == ShelfAnimState.HIDE
+ ? shelfHiddenProgress
+ : mShelfState == ShelfAnimState.PEEK
+ ? shelfPeekingProgress
+ : shelfOverviewProgress;
+ Animator shelfAnim = mLauncher.getStateManager()
+ .createStateElementAnimation(INDEX_SHELF_ANIM, toProgress);
+ shelfAnim.setInterpolator(interpolator);
+ shelfAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mShelfState = ShelfAnimState.CANCEL;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mIsPeeking = mShelfState == ShelfAnimState.PEEK;
+ }
+ });
+ shelfAnim.setDuration(duration).start();
+ }
+
+ /** @return Whether the shelf is currently peeking or animating to or from peeking. */
+ public boolean isPeeking() {
+ return mIsPeeking;
+ }
+
+ /** The various shelf states we can animate to. */
+ public enum ShelfAnimState {
+ HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false);
+
+ ShelfAnimState(boolean shouldPreformHaptic) {
+ this.shouldPreformHaptic = shouldPreformHaptic;
+ }
+
+ public final boolean shouldPreformHaptic;
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 07e9686..958ef7d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -15,32 +15,37 @@
*/
package com.android.quickstep.util;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.view.View;
import android.view.ViewGroup;
-import androidx.annotation.Nullable;
-
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.Workspace;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.anim.SpringObjectAnimator;
+import com.android.launcher3.graphics.OverviewScrim;
+import com.android.quickstep.views.RecentsView;
import java.util.ArrayList;
import java.util.List;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-
/**
* Creates an animation where all the workspace items are moved into their final location,
* staggered row by row from the bottom up.
@@ -58,19 +63,13 @@
private final float mVelocity;
private final float mSpringTransY;
- private final View mViewToIgnore;
private final List<Animator> mAnimators = new ArrayList<>();
- /**
- * @param floatingViewOriginalView The FloatingIconView's original view.
- */
- public StaggeredWorkspaceAnim(Launcher launcher, @Nullable View floatingViewOriginalView,
- float velocity) {
+ public StaggeredWorkspaceAnim(Launcher launcher, float velocity, boolean animateOverviewScrim) {
+ prepareToAnimate(launcher);
+
mVelocity = velocity;
- // We ignore this view since it's visibility and position is controlled by
- // the FloatingIconView.
- mViewToIgnore = floatingViewOriginalView;
// Scale the translationY based on the initial velocity to better sync the workspace items
// with the floating view.
@@ -79,9 +78,24 @@
.getDimensionPixelSize(R.dimen.swipe_up_max_workspace_trans_y);
DeviceProfile grid = launcher.getDeviceProfile();
- ShortcutAndWidgetContainer currentPage = ((CellLayout) launcher.getWorkspace()
- .getChildAt(launcher.getWorkspace().getCurrentPage()))
- .getShortcutsAndWidgets();
+ Workspace workspace = launcher.getWorkspace();
+ CellLayout cellLayout = (CellLayout) workspace.getChildAt(workspace.getCurrentPage());
+ ShortcutAndWidgetContainer currentPage = cellLayout.getShortcutsAndWidgets();
+ ViewGroup hotseat = launcher.getHotseat();
+
+ boolean workspaceClipChildren = workspace.getClipChildren();
+ boolean workspaceClipToPadding = workspace.getClipToPadding();
+ boolean cellLayoutClipChildren = cellLayout.getClipChildren();
+ boolean cellLayoutClipToPadding = cellLayout.getClipToPadding();
+ boolean hotseatClipChildren = hotseat.getClipChildren();
+ boolean hotseatClipToPadding = hotseat.getClipToPadding();
+
+ workspace.setClipChildren(false);
+ workspace.setClipToPadding(false);
+ cellLayout.setClipChildren(false);
+ cellLayout.setClipToPadding(false);
+ hotseat.setClipChildren(false);
+ hotseat.setClipToPadding(false);
// Hotseat and QSB takes up two additional rows.
int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2);
@@ -94,23 +108,65 @@
}
// Set up springs for the hotseat and qsb.
+ ViewGroup hotseatChild = (ViewGroup) hotseat.getChildAt(0);
if (grid.isVerticalBarLayout()) {
- ViewGroup hotseat = (ViewGroup) launcher.getHotseat().getChildAt(0);
- for (int i = hotseat.getChildCount() - 1; i >= 0; i--) {
- View child = hotseat.getChildAt(i);
+ for (int i = hotseatChild.getChildCount() - 1; i >= 0; i--) {
+ View child = hotseatChild.getChildAt(i);
CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
addStaggeredAnimationForView(child, lp.cellY + 1, totalRows);
}
} else {
- View hotseat = launcher.getHotseat().getChildAt(0);
- addStaggeredAnimationForView(hotseat, grid.inv.numRows + 1, totalRows);
+ for (int i = hotseatChild.getChildCount() - 1; i >= 0; i--) {
+ View child = hotseatChild.getChildAt(i);
+ addStaggeredAnimationForView(child, grid.inv.numRows + 1, totalRows);
+ }
View qsb = launcher.findViewById(R.id.search_container_all_apps);
addStaggeredAnimationForView(qsb, grid.inv.numRows + 2, totalRows);
}
- addWorkspaceScrimAnimationForState(launcher, BACKGROUND_APP, 0);
- addWorkspaceScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
+ if (animateOverviewScrim) {
+ addScrimAnimationForState(launcher, BACKGROUND_APP, 0);
+ addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
+ }
+
+ AnimatorListener resetClipListener = new AnimatorListenerAdapter() {
+ int numAnimations = mAnimators.size();
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ numAnimations--;
+ if (numAnimations > 0) {
+ return;
+ }
+
+ workspace.setClipChildren(workspaceClipChildren);
+ workspace.setClipToPadding(workspaceClipToPadding);
+ cellLayout.setClipChildren(cellLayoutClipChildren);
+ cellLayout.setClipToPadding(cellLayoutClipToPadding);
+ hotseat.setClipChildren(hotseatClipChildren);
+ hotseat.setClipToPadding(hotseatClipToPadding);
+ }
+ };
+
+ for (Animator a : mAnimators) {
+ a.addListener(resetClipListener);
+ }
+ }
+
+ /**
+ * Setup workspace with 0 duration to prepare for our staggered animation.
+ */
+ private void prepareToAnimate(Launcher launcher) {
+ LauncherStateManager stateManager = launcher.getStateManager();
+ AnimatorSetBuilder builder = new AnimatorSetBuilder();
+ // setRecentsAttachedToAppWindow() will animate recents out.
+ builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
+ stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 0);
+ builder.build().start();
+
+ // Stop scrolling so that it doesn't interfere with the translation offscreen.
+ launcher.<RecentsView>getOverviewPanel().getScroller().forceFinished(true);
}
/**
@@ -134,10 +190,6 @@
* @param totalRows Total number of rows.
*/
private void addStaggeredAnimationForView(View v, int row, int totalRows) {
- if (v == mViewToIgnore) {
- return;
- }
-
// Invert the rows, because we stagger starting from the bottom of the screen.
int invertedRow = totalRows - row;
// Add 1 to the inverted row so that the bottom most row has a start delay.
@@ -157,13 +209,17 @@
mAnimators.add(alpha);
}
- private void addWorkspaceScrimAnimationForState(Launcher launcher, LauncherState state,
- long duration) {
+ private void addScrimAnimationForState(Launcher launcher, LauncherState state, long duration) {
AnimatorSetBuilder scrimAnimBuilder = new AnimatorSetBuilder();
AnimationConfig scrimAnimConfig = new AnimationConfig();
scrimAnimConfig.duration = duration;
PropertySetter scrimPropertySetter = scrimAnimConfig.getPropertySetter(scrimAnimBuilder);
launcher.getWorkspace().getStateTransitionAnimation().setScrim(scrimPropertySetter, state);
mAnimators.add(scrimAnimBuilder.build());
+ Animator fadeOverviewScrim = ObjectAnimator.ofFloat(
+ launcher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS,
+ state.getOverviewScrimAlpha(launcher));
+ fadeOverviewScrim.setDuration(duration);
+ mAnimators.add(fadeOverviewScrim);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
index 381c27a..3619d3a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
@@ -15,8 +15,8 @@
*/
package com.android.quickstep.util;
-import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR;
-import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import android.graphics.Rect;
@@ -68,25 +68,25 @@
public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
mOnFinishListener.accept(this);
- BACKGROUND_EXECUTOR.execute(() -> {
+ UI_HELPER_EXECUTOR.execute(() -> {
controller.setInputConsumerEnabled(false);
controller.finish(toRecents, sendUserLeaveHint);
if (callback != null) {
- MAIN_THREAD_EXECUTOR.execute(callback);
+ MAIN_EXECUTOR.execute(callback);
}
});
}
public void enableInputConsumer() {
- BACKGROUND_EXECUTOR.submit(() -> {
+ UI_HELPER_EXECUTOR.submit(() -> {
controller.hideCurrentInputMethod();
controller.setInputConsumerEnabled(true);
});
}
public void setWindowThresholdCrossed(boolean thresholdCrossed) {
- BACKGROUND_EXECUTOR.execute(() -> {
+ UI_HELPER_EXECUTOR.execute(() -> {
controller.setAnimationTargetsBehindSystemBars(!thresholdCrossed);
if (mShouldMinimizeSplitScreen && thresholdCrossed) {
// NOTE: As a workaround for conflicting animations (Launcher animating the task
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 7fac813..b06d4bc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -19,6 +19,7 @@
import static android.provider.Settings.ACTION_APP_USAGE_SETTINGS;
import static com.android.launcher3.Utilities.prefixTextWithIcon;
+import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
import android.annotation.TargetApi;
import android.app.ActivityOptions;
@@ -41,7 +42,6 @@
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;
import com.android.systemui.shared.recents.model.Task;
@@ -117,7 +117,7 @@
return;
}
- Utilities.THREAD_POOL_EXECUTOR.execute(() -> {
+ THREAD_POOL_EXECUTOR.execute(() -> {
final AppUsageLimit usageLimit = mLauncherApps.getAppUsageLimit(
task.getTopComponent().getPackageName(),
UserHandle.of(task.key.userId));
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 03441c8..c2cb720 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -35,6 +35,7 @@
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
+import android.widget.FrameLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Hotseat;
@@ -45,11 +46,14 @@
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.views.ScrimView;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.util.ClipAnimationHelper;
import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
import com.android.quickstep.util.LayoutUtils;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.RecentsExtraCard;
/**
* {@link RecentsView} used in Launcher activity
@@ -57,8 +61,29 @@
@TargetApi(Build.VERSION_CODES.O)
public class LauncherRecentsView extends RecentsView<Launcher> implements StateListener {
+ private static final Rect sTempRect = new Rect();
+
private final TransformParams mTransformParams = new TransformParams();
+ private RecentsExtraCard mRecentsExtraCardPlugin;
+ private RecentsExtraViewContainer mRecentsExtraViewContainer;
+ private PluginListener<RecentsExtraCard> mRecentsExtraCardPluginListener =
+ new PluginListener<RecentsExtraCard>() {
+ @Override
+ public void onPluginConnected(RecentsExtraCard recentsExtraCard, Context context) {
+ createRecentsExtraCard();
+ mRecentsExtraCardPlugin = recentsExtraCard;
+ mRecentsExtraCardPlugin.setupView(context, mRecentsExtraViewContainer, mActivity);
+ }
+
+ @Override
+ public void onPluginDisconnected(RecentsExtraCard plugin) {
+ removeView(mRecentsExtraViewContainer);
+ mRecentsExtraCardPlugin = null;
+ mRecentsExtraViewContainer = null;
+ }
+ };
+
public LauncherRecentsView(Context context) {
this(context, null);
}
@@ -144,6 +169,25 @@
LayoutUtils.calculateLauncherTaskSize(getContext(), dp, outRect);
}
+ /**
+ * @return The translationX to apply to this view so that the first task is just offscreen.
+ */
+ public float getOffscreenTranslationX(float recentsScale) {
+ float offscreenX = NORMAL.getOverviewScaleAndTranslation(mActivity).translationX;
+ // Offset since scale pushes tasks outwards.
+ getTaskSize(sTempRect);
+ int taskWidth = sTempRect.width();
+ offscreenX += taskWidth * (recentsScale - 1) / 2;
+ if (mRunningTaskTileHidden) {
+ // The first task is hidden, so offset by its width.
+ offscreenX -= (taskWidth + getPageSpacing()) * recentsScale;
+ }
+ if (isRtl()) {
+ offscreenX = -offscreenX;
+ }
+ return offscreenX;
+ }
+
@Override
protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
@@ -264,4 +308,66 @@
}
return super.shouldStealTouchFromSiblingsBelow(ev);
}
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ PluginManagerWrapper.INSTANCE.get(getContext())
+ .addPluginListener(mRecentsExtraCardPluginListener, RecentsExtraCard.class);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(
+ mRecentsExtraCardPluginListener);
+ }
+
+ @Override
+ protected int computeMinScrollX() {
+ if (canComputeScrollX() && !mIsRtl) {
+ return computeScrollX();
+ }
+ return super.computeMinScrollX();
+ }
+
+ @Override
+ protected int computeMaxScrollX() {
+ if (canComputeScrollX() && mIsRtl) {
+ return computeScrollX();
+ }
+ return super.computeMaxScrollX();
+ }
+
+ private boolean canComputeScrollX() {
+ return mRecentsExtraCardPlugin != null && getTaskViewCount() > 0
+ && !mDisallowScrollToClearAll;
+ }
+
+ private int computeScrollX() {
+ int scrollIndex = getTaskViewStartIndex() - 1;
+ while (scrollIndex >= 0 && getChildAt(scrollIndex) instanceof RecentsExtraViewContainer
+ && ((RecentsExtraViewContainer) getChildAt(scrollIndex)).isScrollable()) {
+ scrollIndex--;
+ }
+ return getScrollForPage(scrollIndex + 1);
+ }
+
+ private void createRecentsExtraCard() {
+ mRecentsExtraViewContainer = new RecentsExtraViewContainer(getContext());
+ FrameLayout.LayoutParams helpCardParams =
+ new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT);
+ mRecentsExtraViewContainer.setLayoutParams(helpCardParams);
+ mRecentsExtraViewContainer.setScrollable(true);
+ addView(mRecentsExtraViewContainer, 0);
+ }
+
+ @Override
+ public void resetTaskVisuals() {
+ super.resetTaskVisuals();
+ if (mRecentsExtraViewContainer != null) {
+ mRecentsExtraViewContainer.setAlpha(mContentAlpha);
+ }
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsExtraViewContainer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsExtraViewContainer.java
new file mode 100644
index 0000000..1ea6d4a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsExtraViewContainer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 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.quickstep.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+/**
+ * Empty view to house recents overview extra card
+ */
+public class RecentsExtraViewContainer extends FrameLayout implements RecentsView.PageCallbacks {
+
+ private boolean mScrollable = false;
+
+ public RecentsExtraViewContainer(Context context) {
+ super(context);
+ }
+
+ public RecentsExtraViewContainer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public RecentsExtraViewContainer(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ /**
+ * Determine whether the view should be scrolled to in the recents overview, similar to the
+ * taskviews.
+ * @return true if viewed should be scrolled to, false if not
+ */
+ public boolean isScrollable() {
+ return mScrollable;
+ }
+
+ public void setScrollable(boolean scrollable) {
+ this.mScrollable = scrollable;
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 6cb2506..ca3b92a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -17,7 +17,6 @@
package com.android.quickstep.views;
import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS;
-
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
@@ -35,6 +34,7 @@
import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
@@ -72,6 +72,7 @@
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ListView;
@@ -95,6 +96,7 @@
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.OverScroller;
import com.android.launcher3.util.PendingAnimation;
import com.android.launcher3.util.Themes;
@@ -108,7 +110,6 @@
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.BackgroundExecutor;
import com.android.systemui.shared.system.LauncherEventUtil;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
@@ -185,7 +186,7 @@
private final ViewPool<TaskView> mTaskViewPool;
private boolean mDwbToastShown;
- private boolean mDisallowScrollToClearAll;
+ protected boolean mDisallowScrollToClearAll;
private boolean mOverlayEnabled;
private boolean mFreezeViewVisibility;
@@ -226,7 +227,7 @@
return;
}
- BackgroundExecutor.get().submit(() -> {
+ UI_HELPER_EXECUTOR.execute(() -> {
TaskView taskView = getTaskView(taskId);
if (taskView == null) {
return;
@@ -269,8 +270,8 @@
private int mTaskListChangeId = -1;
// Only valid until the launcher state changes to NORMAL
- private int mRunningTaskId = -1;
- private boolean mRunningTaskTileHidden;
+ protected int mRunningTaskId = -1;
+ protected boolean mRunningTaskTileHidden;
private Task mTmpRunningTask;
private boolean mRunningTaskIconScaledDown = false;
@@ -287,9 +288,9 @@
private LayoutTransition mLayoutTransition;
@ViewDebug.ExportedProperty(category = "launcher")
- private float mContentAlpha = 1;
+ protected float mContentAlpha = 1;
@ViewDebug.ExportedProperty(category = "launcher")
- private float mFullscreenProgress = 0;
+ protected float mFullscreenProgress = 0;
// Keeps track of task id whose visual state should not be reset
private int mIgnoreResetTaskId = -1;
@@ -304,6 +305,9 @@
private Layout mEmptyTextLayout;
private LiveTileOverlay mLiveTileOverlay;
+ // Keeps track of the index where the first TaskView should be
+ private int mTaskViewStartIndex = 0;
+
private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener =
(inMultiWindowMode) -> {
if (!inMultiWindowMode && mOverviewStateEnabled) {
@@ -327,7 +331,6 @@
mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
.inflate(R.layout.overview_clear_all_button, this, false);
mClearAllButton.setOnClickListener(this::dismissAllTasks);
-
mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
10 /* initial size */);
@@ -428,7 +431,7 @@
super.onViewRemoved(child);
// Clear the task data for the removed child if it was visible
- if (child != mClearAllButton) {
+ if (child instanceof TaskView) {
TaskView taskView = (TaskView) child;
mHasVisibleTaskData.delete(taskView.getTask().key.id);
mTaskViewPool.recycle(taskView);
@@ -442,7 +445,7 @@
public TaskView getTaskView(int taskId) {
for (int i = 0; i < getTaskViewCount(); i++) {
- TaskView tv = (TaskView) getChildAt(i);
+ TaskView tv = getTaskViewAt(i);
if (tv.getTask() != null && tv.getTask().key != null && tv.getTask().key.id == taskId) {
return tv;
}
@@ -527,35 +530,33 @@
return true;
}
- private void applyLoadPlan(ArrayList<Task> tasks) {
+ protected void applyLoadPlan(ArrayList<Task> tasks) {
if (mPendingAnimation != null) {
mPendingAnimation.addEndListener((onEndListener) -> applyLoadPlan(tasks));
return;
}
if (tasks == null || tasks.isEmpty()) {
- removeAllViews();
+ removeTasksViewsAndClearAllButton();
onTaskStackUpdated();
return;
}
- int oldChildCount = getChildCount();
-
// Unload existing visible task data
unloadVisibleTaskData();
- TaskView ignoreRestTaskView =
+ TaskView ignoreResetTaskView =
mIgnoreResetTaskId == -1 ? null : getTaskView(mIgnoreResetTaskId);
final int requiredTaskCount = tasks.size();
if (getTaskViewCount() != requiredTaskCount) {
- if (oldChildCount > 0) {
+ if (indexOfChild(mClearAllButton) != -1) {
removeView(mClearAllButton);
}
- for (int i = getChildCount(); i < requiredTaskCount; i++) {
+ for (int i = getTaskViewCount(); i < requiredTaskCount; i++) {
addView(mTaskViewPool.getView());
}
- while (getChildCount() > requiredTaskCount) {
+ while (getTaskViewCount() > requiredTaskCount) {
removeView(getChildAt(getChildCount() - 1));
}
if (requiredTaskCount > 0) {
@@ -565,17 +566,23 @@
// Rebind and reset all task views
for (int i = requiredTaskCount - 1; i >= 0; i--) {
- final int pageIndex = requiredTaskCount - i - 1;
+ final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex;
final Task task = tasks.get(i);
final TaskView taskView = (TaskView) getChildAt(pageIndex);
taskView.bind(task);
}
- TaskView runningTaskView = getRunningTaskView();
- if (runningTaskView != null) {
- setCurrentPage(indexOfChild(runningTaskView));
+
+ if (mNextPage == INVALID_PAGE) {
+ // Set the current page to the running task, but not if settling on new task.
+ TaskView runningTaskView = getRunningTaskView();
+ if (runningTaskView != null) {
+ setCurrentPage(indexOfChild(runningTaskView));
+ } else if (getTaskViewCount() > 0) {
+ setCurrentPage(indexOfChild(getTaskViewAt(0)));
+ }
}
- if (mIgnoreResetTaskId != -1 && getTaskView(mIgnoreResetTaskId) != ignoreRestTaskView) {
+ if (mIgnoreResetTaskId != -1 && getTaskView(mIgnoreResetTaskId) != ignoreResetTaskView) {
// If the taskView mapping is changing, do not preserve the visuals. Since we are
// mostly preserving the first task, and new taskViews are added to the end, it should
// generally map to the same task.
@@ -586,19 +593,31 @@
updateEnabledOverlays();
}
+ private void removeTasksViewsAndClearAllButton() {
+ for (int i = getTaskViewCount() - 1; i >= 0; i--) {
+ removeView(getTaskViewAt(i));
+ }
+ if (indexOfChild(mClearAllButton) != -1) {
+ removeView(mClearAllButton);
+ }
+ }
+
public int getTaskViewCount() {
- // Account for the clear all button.
- int childCount = getChildCount();
- return childCount == 0 ? 0 : childCount - 1;
+ int taskViewCount = getChildCount() - mTaskViewStartIndex;
+ if (indexOfChild(mClearAllButton) != -1) {
+ taskViewCount--;
+ }
+ return taskViewCount;
}
protected void onTaskStackUpdated() { }
public void resetTaskVisuals() {
for (int i = getTaskViewCount() - 1; i >= 0; i--) {
- TaskView taskView = (TaskView) getChildAt(i);
+ TaskView taskView = getTaskViewAt(i);
if (mIgnoreResetTaskId != taskView.getTask().key.id) {
taskView.resetVisualProperties();
+ taskView.setStableAlpha(mContentAlpha);
}
}
if (mRunningTaskTileHidden) {
@@ -701,7 +720,7 @@
}
/**
- * Iterates through all thet asks, and loads the associated task data for newly visible tasks,
+ * Iterates through all the tasks, and loads the associated task data for newly visible tasks,
* and unloads the associated task data for tasks that are no longer visible.
*/
public void loadVisibleTaskData() {
@@ -712,15 +731,16 @@
}
int centerPageIndex = getPageNearestToCenterOfScreen();
- int numChildren = getTaskViewCount();
+ int numChildren = getChildCount();
int lower = Math.max(0, centerPageIndex - 2);
int upper = Math.min(centerPageIndex + 2, numChildren - 1);
// Update the task data for the in/visible children
- for (int i = 0; i < numChildren; i++) {
- TaskView taskView = (TaskView) getChildAt(i);
+ for (int i = 0; i < getTaskViewCount(); i++) {
+ TaskView taskView = getTaskViewAt(i);
Task task = taskView.getTask();
- boolean visible = lower <= i && i <= upper;
+ int index = indexOfChild(taskView);
+ boolean visible = lower <= index && index <= upper;
if (visible) {
if (task == mTmpRunningTask) {
// Skip loading if this is the task that we are animating into
@@ -795,6 +815,10 @@
return tv == null ? -1 : indexOfChild(tv);
}
+ public int getTaskViewStartIndex() {
+ return mTaskViewStartIndex;
+ }
+
/**
* Reloads the view if anything in recents changed.
*/
@@ -822,7 +846,7 @@
*/
public void onSwipeUpAnimationSuccess() {
if (getRunningTaskView() != null) {
- float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get()
+ float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlay != null
? mLiveTileOverlay.cancelIconAnimation()
: 0f;
animateUpRunningTaskIconScale(startProgress);
@@ -849,12 +873,14 @@
* is called. Also scrolls the view to this task.
*/
public void showCurrentTask(int runningTaskId) {
- if (getChildCount() == 0) {
+ if (getTaskView(runningTaskId) == null) {
+ boolean wasEmpty = getTaskViewCount() == 0;
// Add an empty view for now until the task plan is loaded and applied
final TaskView taskView = mTaskViewPool.getView();
- addView(taskView);
- addView(mClearAllButton);
-
+ addView(taskView, mTaskViewStartIndex);
+ if (wasEmpty) {
+ addView(mClearAllButton);
+ }
// The temporary running task is only used for the duration between the start of the
// gesture and the task list is loaded and applied
mTmpRunningTask = new Task(new Task.TaskKey(runningTaskId, 0, new Intent(),
@@ -917,14 +943,13 @@
if (runningTaskView == null) {
// Launch the first task
if (getTaskViewCount() > 0) {
- getTaskViewAt(0).launchTask(true /* animate */);
+ getTaskViewAt(0).launchTask(true);
}
} else {
- TaskView nextTaskView = getNextTaskView();
- if (nextTaskView != null) {
- nextTaskView.launchTask(true /* animate */);
+ if (getNextTaskView() != null) {
+ getNextTaskView().launchTask(true);
} else {
- runningTaskView.launchTask(true /* animate */);
+ runningTaskView.launchTask(true);
}
}
}
@@ -936,6 +961,10 @@
}
}
+ public boolean isTaskIconScaledDown(TaskView taskView) {
+ return mRunningTaskIconScaledDown && getRunningTaskView() == taskView;
+ }
+
private void applyRunningTaskIconScale() {
TaskView firstTask = getRunningTaskView();
if (firstTask != null) {
@@ -1047,9 +1076,10 @@
if (task != null) {
ActivityManagerWrapper.getInstance().removeTask(task.key.id);
if (shouldLog) {
+ ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key);
mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
- onEndListener.logAction, Direction.UP, index,
- TaskUtils.getLaunchComponentKeyForTask(task.key));
+ onEndListener.logAction, Direction.UP, index, componentKey);
+ mActivity.getStatsLogManager().logTaskDismiss(this, componentKey);
}
}
}
@@ -1183,7 +1213,7 @@
int count = getTaskViewCount();
for (int i = 0; i < count; i++) {
- addDismissedTaskAnimations(getChildAt(i), anim, duration);
+ addDismissedTaskAnimations(getTaskViewAt(i), anim, duration);
}
mPendingAnimation = pendingAnimation;
@@ -1191,7 +1221,7 @@
if (onEndListener.isSuccess) {
// Remove all the task views now
ActivityManagerWrapper.getInstance().removeAllRecentTasks();
- removeAllViews();
+ removeTasksViewsAndClearAllButton();
startHome();
}
mPendingAnimation = null;
@@ -1338,26 +1368,50 @@
child.setAlpha(mContentAlpha);
}
- /**
- * @return The most recent task that is older than the currently running task. If there is
- * currently no running task or there is no task older than it, then return null.
- */
@Nullable
public TaskView getNextTaskView() {
- TaskView runningTaskView = getRunningTaskView();
- if (runningTaskView == null) {
- return null;
- }
- return getTaskViewAt(indexOfChild(runningTaskView) + 1);
+ return getTaskViewAtByAbsoluteIndex(getRunningTaskIndex() + 1);
}
+ @Nullable
+ public TaskView getPreviousTaskView() {
+ return getTaskViewAtByAbsoluteIndex(getRunningTaskIndex() - 1);
+ }
+
+ @Nullable
+ public TaskView getCurrentPageTaskView() {
+ return getTaskViewAtByAbsoluteIndex(getCurrentPage());
+ }
+
+ @Nullable
+ public TaskView getNextPageTaskView() {
+ return getTaskViewAtByAbsoluteIndex(getNextPage());
+ }
+
+ @Nullable
+ public TaskView getTaskViewNearestToCenterOfScreen() {
+ return getTaskViewAtByAbsoluteIndex(getPageNearestToCenterOfScreen());
+ }
+
+ /**
+ * Returns null instead of indexOutOfBoundsError when index is not in range
+ */
+ @Nullable
public TaskView getTaskViewAt(int index) {
- View child = getChildAt(index);
- return child == mClearAllButton ? null : (TaskView) child;
+ return getTaskViewAtByAbsoluteIndex(index + mTaskViewStartIndex);
+ }
+
+ @Nullable
+ private TaskView getTaskViewAtByAbsoluteIndex(int index) {
+ if (index < getChildCount() && index >= 0) {
+ View child = getChildAt(index);
+ return child instanceof TaskView ? (TaskView) child : null;
+ }
+ return null;
}
public void updateEmptyMessage() {
- boolean isEmpty = getChildCount() == 0;
+ boolean isEmpty = getTaskViewCount() == 0;
boolean hasSizeChanged = mLastMeasureSize.x != getWidth()
|| mLastMeasureSize.y != getHeight();
if (isEmpty == mShowEmptyMessage && !hasSizeChanged) {
@@ -1491,7 +1545,7 @@
throw new IllegalStateException("Another pending animation is still running");
}
- int count = getChildCount();
+ int count = getTaskViewCount();
if (count == 0) {
return new PendingAnimation(new AnimatorSet());
}
@@ -1664,18 +1718,38 @@
@Override
protected int computeMinScrollX() {
- if (mIsRtl && mDisallowScrollToClearAll) {
- // We aren't showing the clear all button, so use the leftmost task as the min scroll.
- return getScrollForPage(getTaskViewCount() - 1);
+ if (getTaskViewCount() > 0) {
+ if (mDisallowScrollToClearAll) {
+ // We aren't showing the clear all button,
+ // so use the leftmost task as the min scroll.
+ if (mIsRtl) {
+ return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
+ }
+ return getScrollForPage(mTaskViewStartIndex);
+ }
+ if (mIsRtl) {
+ return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
+ }
+ return getScrollForPage(mTaskViewStartIndex);
}
return super.computeMinScrollX();
}
@Override
protected int computeMaxScrollX() {
- if (!mIsRtl && mDisallowScrollToClearAll) {
- // We aren't showing the clear all button, so use the rightmost task as the max scroll.
- return getScrollForPage(getTaskViewCount() - 1);
+ if (getTaskViewCount() > 0) {
+ if (mDisallowScrollToClearAll) {
+ // We aren't showing the clear all button,
+ // so use the rightmost task as the min scroll.
+ if (mIsRtl) {
+ return getScrollForPage(mTaskViewStartIndex);
+ }
+ return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
+ }
+ if (mIsRtl) {
+ return getScrollForPage(mTaskViewStartIndex);
+ }
+ return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
}
return super.computeMaxScrollX();
}
@@ -1688,6 +1762,9 @@
* @return How many pixels the running task is offset on the x-axis due to the current scrollX.
*/
public float getScrollOffset() {
+ if (getRunningTaskIndex() == -1) {
+ return 0;
+ }
int startScroll = getScrollForPage(getRunningTaskIndex());
int offsetX = startScroll - getScrollX();
offsetX *= getScaleX();
@@ -1724,7 +1801,7 @@
int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1;
int taskCount = getTaskViewCount();
for (int i = 0; i < taskCount; i++) {
- ((TaskView) getChildAt(i)).setOverlayEnabled(i == overlayEnabledPage);
+ getTaskViewAt(i).setOverlayEnabled(i == overlayEnabledPage);
}
}
@@ -1734,4 +1811,35 @@
updateEnabledOverlays();
}
}
+
+ public int getLeftGestureMargin() {
+ final WindowInsets insets = getRootWindowInsets();
+ return Math.max(insets.getSystemGestureInsets().left, insets.getSystemWindowInsetLeft());
+ }
+
+ public int getRightGestureMargin() {
+ final WindowInsets insets = getRootWindowInsets();
+ return Math.max(insets.getSystemGestureInsets().right, insets.getSystemWindowInsetRight());
+ }
+
+ @Override
+ public void addView(View child, int index) {
+ super.addView(child, index);
+ if (isExtraCardView(child, index)) {
+ mTaskViewStartIndex++;
+ }
+ }
+
+ @Override
+ public void removeView(View view) {
+ if (isExtraCardView(view, indexOfChild(view))) {
+ mTaskViewStartIndex--;
+ }
+ super.removeView(view);
+ }
+
+ private boolean isExtraCardView(View view, int index) {
+ return !(view instanceof TaskView) && !(view instanceof ClearAllButton)
+ && index <= mTaskViewStartIndex;
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
index c1f6b82..07d0796 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
@@ -26,6 +26,7 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
@@ -40,6 +41,7 @@
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.TaskOverlayFactory;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index d55a520..044292a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -39,24 +39,28 @@
import android.util.FloatProperty;
import android.util.Property;
import android.view.View;
+import android.view.ViewGroup;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
import com.android.quickstep.util.TaskCornerRadius;
+import com.android.systemui.plugins.OverviewScreenshotActions;
+import com.android.systemui.plugins.PluginListener;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
/**
* A task in the Recents view.
*/
-public class TaskThumbnailView extends View {
+public class TaskThumbnailView extends View implements PluginListener<OverviewScreenshotActions> {
private final static ColorMatrix COLOR_MATRIX = new ColorMatrix();
private final static ColorMatrix SATURATION_COLOR_MATRIX = new ColorMatrix();
@@ -100,6 +104,7 @@
private boolean mOverlayEnabled;
private boolean mRotated;
+ private OverviewScreenshotActions mOverviewScreenshotActionsPlugin;
public TaskThumbnailView(Context context) {
this(context, null);
@@ -147,6 +152,11 @@
mPaint.setShader(null);
mOverlay.reset();
}
+
+ if (mOverviewScreenshotActionsPlugin != null) {
+ mOverviewScreenshotActionsPlugin
+ .setupActions((ViewGroup) getTaskView(), getThumbnail(), mActivity);
+ }
updateThumbnailPaintFilter();
}
@@ -211,6 +221,33 @@
canvas.restore();
}
+ @Override
+ public void onPluginConnected(OverviewScreenshotActions overviewScreenshotActions,
+ Context context) {
+ mOverviewScreenshotActionsPlugin = overviewScreenshotActions;
+ mOverviewScreenshotActionsPlugin.setupActions(getTaskView(), getThumbnail(), mActivity);
+ }
+
+ @Override
+ public void onPluginDisconnected(OverviewScreenshotActions plugin) {
+ if (mOverviewScreenshotActionsPlugin != null) {
+ mOverviewScreenshotActionsPlugin = null;
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ PluginManagerWrapper.INSTANCE.get(getContext())
+ .addPluginListener(this, OverviewScreenshotActions.class);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(this);
+ }
+
public RectF getInsetsToDrawInFullscreen(boolean isMultiWindowMode) {
// Don't show insets in multi window mode.
return isMultiWindowMode ? EMPTY_RECT_F : mClippedInsets;
@@ -410,4 +447,11 @@
return new ColorMatrixColorFilter(COLOR_MATRIX);
}
+
+ public Bitmap getThumbnail() {
+ if (mThumbnailData == null) {
+ return null;
+ }
+ return mThumbnailData.thumbnail;
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index b26fdce..51802df 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -28,7 +28,6 @@
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.res.Resources;
@@ -192,9 +191,6 @@
super(context, attrs, defStyleAttr);
mActivity = BaseDraggingActivity.fromContext(context);
setOnClickListener((view) -> {
- if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_START_TASK_TAG, "TaskView onClick");
- }
if (getTask() == null) {
return;
}
@@ -291,9 +287,6 @@
public void launchTask(boolean animate, boolean freezeTaskList, Consumer<Boolean> resultCallback,
Handler resultCallbackHandler) {
- if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_START_TASK_TAG, "launchTask");
- }
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (isRunningTask()) {
getRecentsView().finishRecentsAnimation(false /* toRecents */,
@@ -308,9 +301,6 @@
private void launchTaskInternal(boolean animate, boolean freezeTaskList,
Consumer<Boolean> resultCallback, Handler resultCallbackHandler) {
- if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_START_TASK_TAG, "launchTaskInternal");
- }
if (mTask != null) {
final ActivityOptions opts;
if (animate) {
@@ -827,8 +817,11 @@
/ (getWidth() + currentInsetsLeft + currentInsetsRight));
}
- // Some of the items in here are dependent on the current fullscreen params
- setIconScaleAndDim(progress, true /* invert */);
+ if (!getRecentsView().isTaskIconScaledDown(this)) {
+ // Some of the items in here are dependent on the current fullscreen params, but don't
+ // update them if the icon is supposed to be scaled down.
+ setIconScaleAndDim(progress, true /* invert */);
+ }
thumbnail.setFullscreenParams(mCurrentFullscreenParams);
mOutlineProvider.setFullscreenParams(mCurrentFullscreenParams);
diff --git a/quickstep/res/values-en-rCA/strings.xml b/quickstep/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..2d1418e
--- /dev/null
+++ b/quickstep/res/values-en-rCA/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Split screen"</string>
+ <string name="recent_task_option_pin" msgid="7929860679018978258">"Pin"</string>
+ <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
+ <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Overview"</string>
+ <string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
+ <string name="accessibility_close_task" msgid="5354563209433803643">"Close"</string>
+ <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
+ <string name="recents_clear_all" msgid="5328176793634888831">"Clear all"</string>
+ <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recent apps"</string>
+ <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
+ <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 minute"</string>
+ <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> left today"</string>
+ <string name="title_app_suggestions" msgid="4185902664111965088">"App suggestions"</string>
+ <string name="all_apps_label" msgid="8542784161730910663">"All apps"</string>
+ <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Your predicted apps"</string>
+</resources>
diff --git a/quickstep/res/values-en-rXC/strings.xml b/quickstep/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..bb186db
--- /dev/null
+++ b/quickstep/res/values-en-rXC/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Split screen"</string>
+ <string name="recent_task_option_pin" msgid="7929860679018978258">"Pin"</string>
+ <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
+ <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Overview"</string>
+ <string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
+ <string name="accessibility_close_task" msgid="5354563209433803643">"Close"</string>
+ <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
+ <string name="recents_clear_all" msgid="5328176793634888831">"Clear all"</string>
+ <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recent apps"</string>
+ <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
+ <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 minute"</string>
+ <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> left today"</string>
+ <string name="title_app_suggestions" msgid="4185902664111965088">"App suggestions"</string>
+ <string name="all_apps_label" msgid="8542784161730910663">"All apps"</string>
+ <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Your predicted apps"</string>
+</resources>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index 5394f49..01dcff2 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -33,5 +33,5 @@
<string name="time_left_for_app" msgid="3111996412933644358">"Encore <xliff:g id="TIME">%1$s</xliff:g> aujourd\'hui"</string>
<string name="title_app_suggestions" msgid="4185902664111965088">"Suggestions d\'applications"</string>
<string name="all_apps_label" msgid="8542784161730910663">"Toutes les applications"</string>
- <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Vos applications prévues"</string>
+ <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Applications prévues pour vous"</string>
</resources>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index 0467af4..387d509 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -27,7 +27,7 @@
<string name="accessibility_close_task" msgid="5354563209433803643">"बंद करें"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ऐप्लिकेशन इस्तेमाल की सेटिंग"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"सभी ऐप्लिकेशन बंद करें"</string>
- <string name="accessibility_recent_apps" msgid="4058661986695117371">"हाल ही में इस्तेमाल किए गए एेप्लिकेशन"</string>
+ <string name="accessibility_recent_apps" msgid="4058661986695117371">"हाल ही में इस्तेमाल किए गए ऐप्लिकेशन"</string>
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"<1 मिनट"</string>
<string name="time_left_for_app" msgid="3111996412933644358">"आज <xliff:g id="TIME">%1$s</xliff:g> और चलेगा"</string>
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index 1ca558a..cccece7 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -27,7 +27,7 @@
<string name="accessibility_close_task" msgid="5354563209433803643">"बंद"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"अॅप वापर सेटिंग्ज"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"सर्व साफ करा"</string>
- <string name="accessibility_recent_apps" msgid="4058661986695117371">"अलीकडील अॅप्स"</string>
+ <string name="accessibility_recent_apps" msgid="4058661986695117371">"अलीकडील अॅप्स"</string>
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"१मिहून कमी"</string>
<string name="time_left_for_app" msgid="3111996412933644358">"आज <xliff:g id="TIME">%1$s</xliff:g>शिल्लक आहे"</string>
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 5c4d6d8..292eaaa 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -21,8 +21,6 @@
<!-- Activity which blocks home gesture -->
<string name="gesture_blocking_activity" translatable="false"></string>
- <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
-
<string name="stats_log_manager_class" translatable="false">com.android.quickstep.logging.StatsLogCompatManager</string>
<string name="test_information_handler_class" translatable="false">com.android.quickstep.QuickstepTestInformationHandler</string>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index b0968f9..78424ca 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -69,6 +69,7 @@
<!-- Distance from the vertical edges of the screen in which assist gestures are recognized -->
<dimen name="gestures_assistant_width">48dp</dimen>
<dimen name="gestures_assistant_drag_threshold">55dp</dimen>
+ <dimen name="gestures_assistant_fling_threshold">55dp</dimen>
<!-- Distance to move elements when swiping up to go home from launcher -->
<dimen name="home_pullback_distance">28dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index 78f6ffa..a8e2956 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -15,8 +15,8 @@
*/
package com.android.launcher3;
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
import static com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import static com.android.systemui.shared.recents.utilities.Utilities
.postAtFrontOfQueueAsynchronously;
@@ -24,6 +24,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.annotation.TargetApi;
+import android.content.Context;
import android.os.Build;
import android.os.Handler;
@@ -66,7 +67,7 @@
/**
* Called on the UI thread when the animation targets are received. The implementation must
- * call {@link AnimationResult#setAnimation(AnimatorSet)} with the target animation to be run.
+ * call {@link AnimationResult#setAnimation} with the target animation to be run.
*/
@UiThread
public abstract void onCreateAnimation(
@@ -110,7 +111,7 @@
}
@UiThread
- public void setAnimation(AnimatorSet animation) {
+ public void setAnimation(AnimatorSet animation, Context context) {
if (mInitialized) {
throw new IllegalStateException("Animation already initialized");
}
@@ -134,7 +135,7 @@
// Because t=0 has the app icon in its original spot, we can skip the
// first frame and have the same movement one frame earlier.
- mAnimator.setCurrentPlayTime(SINGLE_FRAME_MS);
+ mAnimator.setCurrentPlayTime(getSingleFrameMs(context));
}
}
}
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index c5c5323..b9ce1ce 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -85,7 +85,7 @@
register();
- Bundle options = animProvider.toActivityOptions(handler, duration).toBundle();
+ Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle();
context.startActivity(addToIntent(new Intent((intent))), options);
}
}
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index b60a017..a91410c 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -57,6 +57,9 @@
import android.util.Pair;
import android.view.View;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.Interpolators;
@@ -69,6 +72,7 @@
import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.util.ShelfPeekAnim;
import com.android.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.QuickStepContract;
@@ -80,9 +84,6 @@
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
import com.android.systemui.shared.system.WindowManagerWrapper;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
/**
* {@link LauncherAppTransitionManager} with Quickstep-specific app transitions for launching from
* home and/or all-apps.
@@ -150,6 +151,8 @@
private RemoteAnimationProvider mRemoteAnimationProvider;
+ private final ShelfPeekAnim mShelfPeekAnim;
+
private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -177,6 +180,12 @@
mLauncher.addOnDeviceProfileChangeListener(this);
registerRemoteAnimations();
+
+ mShelfPeekAnim = new ShelfPeekAnim(mLauncher);
+ }
+
+ public ShelfPeekAnim getShelfPeekAnim() {
+ return mShelfPeekAnim;
}
@Override
@@ -219,7 +228,7 @@
anim.addListener(mForceInvisibleListener);
}
- result.setAnimation(anim);
+ result.setAnimation(anim, mLauncher);
}
};
@@ -822,7 +831,7 @@
}
mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
- result.setAnimation(anim);
+ result.setAnimation(anim, mLauncher);
}
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index f0204b9..174e49b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -19,16 +19,20 @@
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCRIM_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y;
import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
import android.util.FloatProperty;
import android.view.View;
import android.view.animation.Interpolator;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherState.ScaleAndTranslation;
@@ -36,8 +40,7 @@
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.PropertySetter;
-
-import androidx.annotation.NonNull;
+import com.android.launcher3.graphics.OverviewScrim;
/**
* State handler for recents view. Manages UI changes and animations for recents view based off the
@@ -67,6 +70,8 @@
mRecentsView.setTranslationX(translationX);
mRecentsView.setTranslationY(scaleAndTranslation.translationY);
getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
+ OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
+ SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher));
}
@Override
@@ -110,6 +115,9 @@
translateYInterpolator);
setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
+ OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
+ setter.setFloat(scrim, SCRIM_PROGRESS, toState.getOverviewScrimAlpha(mLauncher),
+ builder.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
}
/**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java b/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java
new file mode 100644
index 0000000..853a1c6
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 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.uioverrides;
+
+import android.content.Context;
+import android.provider.DeviceConfig;
+import com.android.launcher3.config.BaseFlags.BaseTogglableFlag;
+
+public class TogglableFlag extends BaseTogglableFlag {
+ public static final String NAMESPACE_LAUNCHER = "launcher";
+ public static final String TAG = "TogglableFlag";
+
+ public TogglableFlag(String key, boolean defaultValue, String description) {
+ super(key, defaultValue, description);
+ }
+
+ @Override
+ public boolean getOverridenDefaultValue(boolean value) {
+ return DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, getKey(), value);
+ }
+
+ @Override
+ public void addChangeListener(Context context, Runnable r) {
+ DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_LAUNCHER,
+ context.getMainExecutor(),
+ (properties) -> {
+ if (!NAMESPACE_LAUNCHER.equals(properties.getNamespace())) {
+ return;
+ }
+ initialize(context);
+ r.run();
+ });
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 97cd38a..0790cf6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -28,13 +28,16 @@
import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN;
import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_COUNT;
import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.app.Activity;
+import android.app.Person;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.pm.ShortcutInfo;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.util.Base64;
@@ -56,6 +59,7 @@
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
import com.android.systemui.shared.system.ActivityCompat;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
@@ -244,4 +248,15 @@
}
return new ScaleAndTranslation(1.1f, 0f, 0f);
}
+
+ public static Person[] getPersons(ShortcutInfo si) {
+ Person[] persons = si.getPersons();
+ return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons;
+ }
+
+ /** Closes system windows. */
+ public static void closeSystemWindows() {
+ ActivityManagerWrapper.getInstance()
+ .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
index 910fa0d..7beb9db 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
@@ -14,16 +14,17 @@
package com.android.launcher3.uioverrides.plugins;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
import android.content.Context;
import android.os.Looper;
-import com.android.launcher3.LauncherModel;
import com.android.systemui.shared.plugins.PluginInitializer;
public class PluginInitializerImpl implements PluginInitializer {
@Override
public Looper getBgLooper() {
- return LauncherModel.getWorkerLooper();
+ return MODEL_EXECUTOR.getLooper();
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
index 0605953..39b0f8d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
@@ -11,7 +11,7 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.AnimationComponents;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.quickstep.RecentsModel;
@@ -24,7 +24,7 @@
private static final String TAG = "LandscapeEdgeSwipeCtrl";
public LandscapeEdgeSwipeController(Launcher l) {
- super(l, SwipeDetector.HORIZONTAL);
+ super(l, SingleAxisSwipeDetector.HORIZONTAL);
}
@Override
@@ -46,7 +46,7 @@
}
@Override
- protected int getLogContainerTypeForNormalState() {
+ protected int getLogContainerTypeForNormalState(MotionEvent ev) {
return LauncherLogProto.ContainerType.NAVBAR;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 6030cea..db6a40f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -43,7 +43,7 @@
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.uioverrides.states.OverviewState;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -51,7 +51,6 @@
import com.android.quickstep.RecentsModel;
import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.util.LayoutUtils;
-import com.android.systemui.shared.system.QuickStepContract;
/**
* Touch controller for handling various state transitions in portrait UI.
@@ -63,7 +62,7 @@
/**
* The progress at which all apps content will be fully visible when swiping up from overview.
*/
- private static final float ALL_APPS_CONTENT_FADE_THRESHOLD = 0.08f;
+ protected static final float ALL_APPS_CONTENT_FADE_THRESHOLD = 0.08f;
/**
* The progress at which recents will begin fading out when swiping up from overview.
@@ -80,7 +79,7 @@
private boolean mFinishFastOnSecondTouch;
public PortraitStatesTouchController(Launcher l, boolean allowDragToOverview) {
- super(l, SwipeDetector.VERTICAL);
+ super(l, SingleAxisSwipeDetector.VERTICAL);
mOverviewPortraitStateTouchHelper = new PortraitOverviewStateTouchHelper(l);
mAllowDragToOverview = allowDragToOverview;
}
@@ -148,8 +147,8 @@
}
@Override
- protected int getLogContainerTypeForNormalState() {
- return ContainerType.HOTSEAT;
+ protected int getLogContainerTypeForNormalState(MotionEvent ev) {
+ return isTouchOverHotseat(mLauncher, ev) ? ContainerType.HOTSEAT : ContainerType.WORKSPACE;
}
private AnimatorSetBuilder getNormalToOverviewAnimation() {
@@ -178,6 +177,20 @@
return builder;
}
+ private AnimatorSetBuilder getNormalToAllAppsAnimation() {
+ AnimatorSetBuilder builder = new AnimatorSetBuilder();
+ builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(ACCEL,
+ 0, ALL_APPS_CONTENT_FADE_THRESHOLD));
+ return builder;
+ }
+
+ private AnimatorSetBuilder getAllAppsToNormalAnimation() {
+ AnimatorSetBuilder builder = new AnimatorSetBuilder();
+ builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(DEACCEL,
+ 1 - ALL_APPS_CONTENT_FADE_THRESHOLD, 1));
+ return builder;
+ }
+
@Override
protected AnimatorSetBuilder getAnimatorSetBuilderForStates(LauncherState fromState,
LauncherState toState) {
@@ -188,6 +201,10 @@
builder = getOverviewToAllAppsAnimation();
} else if (fromState == ALL_APPS && toState == OVERVIEW) {
builder = getAllAppsToOverviewAnimation();
+ } else if (fromState == NORMAL && toState == ALL_APPS) {
+ builder = getNormalToAllAppsAnimation();
+ } else if (fromState == ALL_APPS && toState == NORMAL) {
+ builder = getAllAppsToNormalAnimation();
}
return builder;
}
@@ -296,9 +313,13 @@
* @return true if the event is over the hotseat
*/
static boolean isTouchOverHotseat(Launcher launcher, MotionEvent ev) {
+ return (ev.getY() >= getHotseatTop(launcher));
+ }
+
+ public static int getHotseatTop(Launcher launcher) {
DeviceProfile dp = launcher.getDeviceProfile();
int hotseatHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
- return (ev.getY() >= (launcher.getDragLayer().getHeight() - hotseatHeight));
+ return launcher.getDragLayer().getHeight() - hotseatHeight;
}
private static class InterpolatorWrapper implements Interpolator {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index fee1820..11a8043 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -17,17 +17,25 @@
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+import static android.view.MotionEvent.ACTION_CANCEL;
+import android.graphics.PointF;
import android.os.RemoteException;
import android.util.Log;
+import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
+import android.view.Window;
+import android.view.WindowManager;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.touch.TouchEventTranslator;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.RecentsModel;
import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -36,18 +44,29 @@
/**
* TouchController for handling touch events that get sent to the StatusBar. Once the
- * Once the event delta y passes the touch slop, the events start getting forwarded.
+ * Once the event delta mDownY passes the touch slop, the events start getting forwarded.
* All events are offset by initial Y value of the pointer.
*/
public class StatusBarTouchController implements TouchController {
private static final String TAG = "StatusBarController";
+ /**
+ * Window flag: Enable touches to slide out of a window into neighboring
+ * windows in mid-gesture instead of being captured for the duration of
+ * the gesture.
+ *
+ * This flag changes the behavior of touch focus for this window only.
+ * Touches can slide out of the window but they cannot necessarily slide
+ * back in (unless the other window with touch focus permits it).
+ */
+ private static final int FLAG_SLIPPERY = 0x20000000;
+
protected final Launcher mLauncher;
- protected final TouchEventTranslator mTranslator;
private final float mTouchSlop;
private ISystemUiProxy mSysUiProxy;
private int mLastAction;
+ private final SparseArray<PointF> mDownEvents;
/* If {@code false}, this controller should not handle the input {@link MotionEvent}.*/
private boolean mCanIntercept;
@@ -56,7 +75,7 @@
mLauncher = l;
// Guard against TAPs by increasing the touch slop.
mTouchSlop = 2 * ViewConfiguration.get(l).getScaledTouchSlop();
- mTranslator = new TouchEventTranslator((MotionEvent ev)-> dispatchTouchEvent(ev));
+ mDownEvents = new SparseArray<>();
}
@Override
@@ -64,7 +83,6 @@
writer.println(prefix + "mCanIntercept:" + mCanIntercept);
writer.println(prefix + "mLastAction:" + MotionEvent.actionToString(mLastAction));
writer.println(prefix + "mSysUiProxy available:" + (mSysUiProxy != null));
-
}
private void dispatchTouchEvent(MotionEvent ev) {
@@ -81,26 +99,31 @@
@Override
public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
int action = ev.getActionMasked();
+ int idx = ev.getActionIndex();
+ int pid = ev.getPointerId(idx);
if (action == ACTION_DOWN) {
mCanIntercept = canInterceptTouch(ev);
if (!mCanIntercept) {
return false;
}
- mTranslator.reset();
- mTranslator.setDownParameters(0, ev);
+ mDownEvents.put(pid, new PointF(ev.getX(), ev.getY()));
} else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
- // Check!! should only set it only when threshold is not entered.
- mTranslator.setDownParameters(ev.getActionIndex(), ev);
+ // Check!! should only set it only when threshold is not entered.
+ mDownEvents.put(pid, new PointF(ev.getX(idx), ev.getY(idx)));
}
if (!mCanIntercept) {
return false;
}
if (action == ACTION_MOVE) {
- float dy = ev.getY() - mTranslator.getDownY();
- float dx = ev.getX() - mTranslator.getDownX();
- if (dy > mTouchSlop && dy > Math.abs(dx)) {
- mTranslator.dispatchDownEvents(ev);
- mTranslator.processMotionEvent(ev);
+ float dy = ev.getY(idx) - mDownEvents.get(pid).y;
+ float dx = ev.getX(idx) - mDownEvents.get(pid).x;
+ // Currently input dispatcher will not do touch transfer if there are more than
+ // one touch pointer. Hence, even if slope passed, only set the slippery flag
+ // when there is single touch event. (context: InputDispatcher.cpp line 1445)
+ if (dy > mTouchSlop && dy > Math.abs(dx) && ev.getPointerCount() == 1) {
+ ev.setAction(ACTION_DOWN);
+ dispatchTouchEvent(ev);
+ setWindowSlippery(true);
return true;
}
if (Math.abs(dx) > mTouchSlop) {
@@ -110,13 +133,31 @@
return false;
}
-
@Override
public final boolean onControllerTouchEvent(MotionEvent ev) {
- mTranslator.processMotionEvent(ev);
+ int action = ev.getAction();
+ if (action == ACTION_UP || action == ACTION_CANCEL) {
+ dispatchTouchEvent(ev);
+ mLauncher.getUserEventDispatcher().logActionOnContainer(action == ACTION_UP ?
+ Touch.FLING : Touch.SWIPE, Direction.DOWN, ContainerType.WORKSPACE,
+ mLauncher.getWorkspace().getCurrentPage());
+ setWindowSlippery(false);
+ return true;
+ }
return true;
}
+ private void setWindowSlippery(boolean enable) {
+ Window w = mLauncher.getWindow();
+ WindowManager.LayoutParams wlp = w.getAttributes();
+ if (enable) {
+ wlp.flags |= FLAG_SLIPPERY;
+ } else {
+ wlp.flags &= ~FLAG_SLIPPERY;
+ }
+ w.setAttributes(wlp);
+ }
+
private boolean canInterceptTouch(MotionEvent ev) {
if (!mLauncher.isInState(LauncherState.NORMAL) ||
AbstractFloatingView.getTopOpenViewWithType(mLauncher,
@@ -132,4 +173,4 @@
mSysUiProxy = RecentsModel.INSTANCE.get(mLauncher).getSystemUiProxy();
return mSysUiProxy != null;
}
-}
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index cd2c9cb..110cc23 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -36,6 +36,7 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.util.ShelfPeekAnim;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import java.util.function.BiPredicate;
@@ -109,16 +110,6 @@
interface AnimationFactory {
- enum ShelfAnimState {
- HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false);
-
- ShelfAnimState(boolean shouldPreformHaptic) {
- this.shouldPreformHaptic = shouldPreformHaptic;
- }
-
- public final boolean shouldPreformHaptic;
- }
-
default void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) { }
void createActivityController(long transitionLength);
@@ -127,8 +118,8 @@
default void onTransitionCancelled() { }
- default void setShelfState(ShelfAnimState animState, Interpolator interpolator,
- long duration) { }
+ default void setShelfState(ShelfPeekAnim.ShelfAnimState animState,
+ Interpolator interpolator, long duration) { }
/**
* @param attached Whether to show RecentsView alongside the app window. If false, recents
@@ -152,5 +143,15 @@
default void playAtomicAnimation(float velocity) {
// No-op
}
+
+ static RectF getDefaultWindowTargetRect(DeviceProfile dp) {
+ final int halfIconSize = dp.iconSizePx / 2;
+ final float targetCenterX = dp.availableWidthPx / 2f;
+ final float targetCenterY = dp.availableHeightPx - dp.hotseatBarSizePx;
+ // Fallback to animate to center of screen.
+ return new RectF(targetCenterX - halfIconSize, targetCenterY - halfIconSize,
+ targetCenterX + halfIconSize, targetCenterY + halfIconSize);
+ }
+
}
}
diff --git a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
index c840132..1ac7ed4 100644
--- a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
@@ -157,6 +157,6 @@
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
super.dump(prefix, fd, writer, args);
writer.println(prefix + "Misc:");
- dumpMisc(writer);
+ dumpMisc(prefix + "\t", writer);
}
}
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 0738aff..93a6127 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import android.content.BroadcastReceiver;
@@ -29,11 +30,15 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.util.SparseIntArray;
import com.android.systemui.shared.system.PackageManagerWrapper;
import java.util.ArrayList;
+import java.util.Objects;
/**
* Class to keep track of the current overview component based off user preferences and app updates
@@ -53,22 +58,41 @@
}
};
private final Context mContext;
- private final ComponentName mMyHomeComponent;
+ private final Intent mCurrentHomeIntent;
+ private final Intent mMyHomeIntent;
+ private final Intent mFallbackIntent;
+ private final SparseIntArray mConfigChangesMap = new SparseIntArray();
private String mUpdateRegisteredPackage;
private ActivityControlHelper mActivityControlHelper;
private Intent mOverviewIntent;
- private Intent mHomeIntent;
private int mSystemUiStateFlags;
private boolean mIsHomeAndOverviewSame;
+ private boolean mIsDefaultHome;
public OverviewComponentObserver(Context context) {
mContext = context;
- Intent myHomeIntent = new Intent(Intent.ACTION_MAIN)
+ mCurrentHomeIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
- .setPackage(mContext.getPackageName());
- ResolveInfo info = context.getPackageManager().resolveActivity(myHomeIntent, 0);
- mMyHomeComponent = new ComponentName(context.getPackageName(), info.activityInfo.name);
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mMyHomeIntent = new Intent(mCurrentHomeIntent).setPackage(mContext.getPackageName());
+ ResolveInfo info = context.getPackageManager().resolveActivity(mMyHomeIntent, 0);
+ ComponentName myHomeComponent =
+ new ComponentName(context.getPackageName(), info.activityInfo.name);
+ mMyHomeIntent.setComponent(myHomeComponent);
+ mConfigChangesMap.append(myHomeComponent.hashCode(), info.activityInfo.configChanges);
+
+ ComponentName fallbackComponent = new ComponentName(mContext, RecentsActivity.class);
+ mFallbackIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .setComponent(fallbackComponent)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ try {
+ ActivityInfo fallbackInfo = context.getPackageManager().getActivityInfo(
+ mFallbackIntent.getComponent(), 0 /* flags */);
+ mConfigChangesMap.append(fallbackComponent.hashCode(), fallbackInfo.configChanges);
+ } catch (PackageManager.NameNotFoundException ignored) { /* Impossible */ }
mContext.registerReceiver(mUserPreferenceChangeReceiver,
new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED));
@@ -84,6 +108,10 @@
}
}
+ public boolean assistantGestureIsConstrained() {
+ return (mSystemUiStateFlags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0;
+ }
+
/**
* Update overview intent and {@link ActivityControlHelper} based off the current launcher home
* component.
@@ -92,17 +120,22 @@
ComponentName defaultHome = PackageManagerWrapper.getInstance()
.getHomeActivities(new ArrayList<>());
- final String overviewIntentCategory;
- ComponentName overviewComponent;
- mHomeIntent = null;
+ mIsDefaultHome = Objects.equals(mMyHomeIntent.getComponent(), defaultHome);
- if ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0 &&
- (defaultHome == null || mMyHomeComponent.equals(defaultHome))) {
+ // Set assistant visibility to 0 from launcher's perspective, ensures any elements that
+ // launcher made invisible become visible again before the new activity control helper
+ // becomes active.
+ if (mActivityControlHelper != null) {
+ mActivityControlHelper.onAssistantVisibilityChanged(0.f);
+ }
+
+ if ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
+ && (defaultHome == null || mIsDefaultHome)) {
// User default home is same as out home app. Use Overview integrated in Launcher.
- overviewComponent = mMyHomeComponent;
mActivityControlHelper = new LauncherActivityControllerHelper();
mIsHomeAndOverviewSame = true;
- overviewIntentCategory = Intent.CATEGORY_HOME;
+ mOverviewIntent = mMyHomeIntent;
+ mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent());
if (mUpdateRegisteredPackage != null) {
// Remove any update listener as we don't care about other packages.
@@ -111,14 +144,12 @@
}
} else {
// The default home app is a different launcher. Use the fallback Overview instead.
- overviewComponent = new ComponentName(mContext, RecentsActivity.class);
+
mActivityControlHelper = new FallbackActivityControllerHelper();
mIsHomeAndOverviewSame = false;
- overviewIntentCategory = Intent.CATEGORY_DEFAULT;
+ mOverviewIntent = mFallbackIntent;
+ mCurrentHomeIntent.setComponent(defaultHome);
- mHomeIntent = new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME)
- .setComponent(defaultHome);
// User's default home app can change as a result of package updates of this app (such
// as uninstalling the app or removing the "Launcher" feature in an update).
// Listen for package updates of this app (and remove any previously attached
@@ -138,14 +169,6 @@
ACTION_PACKAGE_REMOVED));
}
}
-
- mOverviewIntent = new Intent(Intent.ACTION_MAIN)
- .addCategory(overviewIntentCategory)
- .setComponent(overviewComponent)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- if (mHomeIntent == null) {
- mHomeIntent = mOverviewIntent;
- }
}
/**
@@ -161,6 +184,32 @@
}
/**
+ * @return {@code true} if the overview component is able to handle the configuration changes.
+ */
+ boolean canHandleConfigChanges(ComponentName component, int changes) {
+ final int orientationChange =
+ ActivityInfo.CONFIG_ORIENTATION | ActivityInfo.CONFIG_SCREEN_SIZE;
+ if ((changes & orientationChange) == orientationChange) {
+ // This is just an approximate guess for simple orientation change because the changes
+ // may contain non-public bits (e.g. window configuration).
+ return true;
+ }
+
+ int configMask = mConfigChangesMap.get(component.hashCode());
+ return configMask != 0 && (~configMask & changes) == 0;
+ }
+
+ /**
+ * Get the intent for overview activity. It is used when lockscreen is shown and home was died
+ * in background, we still want to restart the one that will be used after unlock.
+ *
+ * @return the overview intent
+ */
+ Intent getOverviewIntentIgnoreSysUiState() {
+ return mIsDefaultHome ? mMyHomeIntent : mOverviewIntent;
+ }
+
+ /**
* Get the current intent for going to the overview activity.
*
* @return the overview intent
@@ -173,7 +222,7 @@
* Get the current intent for going to the home activity.
*/
public Intent getHomeIntent() {
- return mHomeIntent;
+ return mCurrentHomeIntent;
}
/**
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 78b48d7..858c3b6 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -15,20 +15,21 @@
*/
package com.android.quickstep;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
+import androidx.annotation.WorkerThread;
+
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.UiThreadHelper;
import com.android.systemui.shared.recents.ISystemUiProxy;
-import androidx.annotation.WorkerThread;
-
/**
* Sets alpha for the back button
*/
@@ -62,7 +63,7 @@
// because of its high send frequency and data may be very different than the previous value
// For example, send back alpha on uihandler to avoid flickering when setting its visibility
mUiHandler = new Handler(this::handleUiMessage);
- mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleBgMessage);
+ mBgHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleBgMessage);
onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(context)
.addModeChangeListener(this::onNavigationModeChanged));
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index 7bfa9a0..befeee0 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -15,7 +15,6 @@
*/
package com.android.quickstep;
-import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.UserManager;
@@ -23,29 +22,17 @@
import com.android.launcher3.BuildConfig;
import com.android.launcher3.MainProcessInitializer;
-import com.android.launcher3.Utilities;
import com.android.systemui.shared.system.ThreadedRendererCompat;
@SuppressWarnings("unused")
public class QuickstepProcessInitializer extends MainProcessInitializer {
private static final String TAG = "QuickstepProcessInitializer";
- private static final int HEAP_LIMIT_MB = 250;
public QuickstepProcessInitializer(Context context) { }
@Override
protected void init(Context context) {
- if (Utilities.IS_DEBUG_DEVICE) {
- try {
- // Trigger a heap dump if the PSS reaches beyond the target heap limit
- final ActivityManager am = context.getSystemService(ActivityManager.class);
- am.setWatchHeapLimit(HEAP_LIMIT_MB * 1024 * 1024);
- } catch (SecurityException e) {
- // Do nothing
- }
- }
-
// Workaround for b/120550382, an external app can cause the launcher process to start for
// a work profile user which we do not support. Disable the application immediately when we
// detect this to be the case.
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
deleted file mode 100644
index 8951363..0000000
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.android.quickstep;
-
-import android.content.Context;
-import android.os.Bundle;
-
-import com.android.launcher3.testing.TestInformationHandler;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.uioverrides.states.OverviewState;
-import com.android.quickstep.util.LayoutUtils;
-
-public class QuickstepTestInformationHandler extends TestInformationHandler {
-
- public QuickstepTestInformationHandler(Context context) { }
-
- @Override
- public Bundle call(String method) {
- final Bundle response = new Bundle();
- switch (method) {
- case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
- final float swipeHeight =
- OverviewState.getDefaultSwipeHeight(mDeviceProfile);
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
- return response;
- }
-
- case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: {
- final float swipeHeight =
- LayoutUtils.getShelfTrackingDistance(mContext, mDeviceProfile);
- response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
- return response;
- }
- }
-
- return super.call(method);
- }
-}
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index f27ba85..10f9feb 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -16,20 +16,22 @@
package com.android.quickstep;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
import android.annotation.TargetApi;
import android.app.ActivityManager;
-import android.content.Context;
import android.os.Build;
import android.os.Process;
import android.util.SparseBooleanArray;
-import com.android.launcher3.MainThreadExecutor;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.util.LooperExecutor;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.BackgroundExecutor;
import com.android.systemui.shared.system.KeyguardManagerCompat;
-import com.android.systemui.shared.system.RecentTaskInfoCompat;
-import com.android.systemui.shared.system.TaskDescriptionCompat;
import com.android.systemui.shared.system.TaskStackChangeListener;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -42,8 +44,8 @@
public class RecentTasksList extends TaskStackChangeListener {
private final KeyguardManagerCompat mKeyguardManager;
- private final MainThreadExecutor mMainThreadExecutor;
- private final BackgroundExecutor mBgThreadExecutor;
+ private final LooperExecutor mMainThreadExecutor;
+ private final ActivityManagerWrapper mActivityManagerWrapper;
// The list change id, increments as the task list changes in the system
private int mChangeId;
@@ -54,12 +56,13 @@
ArrayList<Task> mTasks = new ArrayList<>();
- public RecentTasksList(Context context) {
- mMainThreadExecutor = new MainThreadExecutor();
- mBgThreadExecutor = BackgroundExecutor.get();
- mKeyguardManager = new KeyguardManagerCompat(context);
+ public RecentTasksList(LooperExecutor mainThreadExecutor,
+ KeyguardManagerCompat keyguardManager, ActivityManagerWrapper activityManagerWrapper) {
+ mMainThreadExecutor = mainThreadExecutor;
+ mKeyguardManager = keyguardManager;
mChangeId = 1;
- ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
+ mActivityManagerWrapper = activityManagerWrapper;
+ mActivityManagerWrapper.registerTaskStackListener(this);
}
/**
@@ -67,7 +70,7 @@
*/
public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) {
// Kick off task loading in the background
- mBgThreadExecutor.submit(() -> {
+ UI_HELPER_EXECUTOR.execute(() -> {
ArrayList<Task> tasks = loadTasksInBackground(numTasks, true /* loadKeysOnly */);
mMainThreadExecutor.execute(() -> callback.accept(tasks));
});
@@ -87,13 +90,14 @@
: () -> callback.accept(copyOf(mTasks));
if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) {
- // The list is up to date, callback with the same list
- mMainThreadExecutor.execute(resultCallback);
+ // The list is up to date, send the callback on the next frame,
+ // so that requestID can be returned first.
+ mMainThreadExecutor.post(resultCallback);
return requestLoadId;
}
// Kick off task loading in the background
- mBgThreadExecutor.submit(() -> {
+ UI_HELPER_EXECUTOR.execute(() -> {
ArrayList<Task> tasks = loadTasksInBackground(Integer.MAX_VALUE, loadKeysOnly);
mMainThreadExecutor.execute(() -> {
@@ -121,12 +125,7 @@
@Override
public void onTaskRemoved(int taskId) {
- for (int i = mTasks.size() - 1; i >= 0; i--) {
- if (mTasks.get(i).key.id == taskId) {
- mTasks.remove(i);
- return;
- }
- }
+ mTasks = loadTasksInBackground(Integer.MAX_VALUE, false);
}
@Override
@@ -143,12 +142,13 @@
/**
* Loads and creates a list of all the recent tasks.
*/
- private ArrayList<Task> loadTasksInBackground(int numTasks,
+ @VisibleForTesting
+ ArrayList<Task> loadTasksInBackground(int numTasks,
boolean loadKeysOnly) {
int currentUserId = Process.myUserHandle().getIdentifier();
ArrayList<Task> allTasks = new ArrayList<>();
List<ActivityManager.RecentTaskInfo> rawTasks =
- ActivityManagerWrapper.getInstance().getRecentTasks(numTasks, currentUserId);
+ mActivityManagerWrapper.getRecentTasks(numTasks, currentUserId);
// The raw tasks are given in most-recent to least-recent order, we need to reverse it
Collections.reverse(rawTasks);
@@ -166,15 +166,11 @@
int taskCount = rawTasks.size();
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo rawTask = rawTasks.get(i);
- RecentTaskInfoCompat t = new RecentTaskInfoCompat(rawTask);
Task.TaskKey taskKey = new Task.TaskKey(rawTask);
Task task;
if (!loadKeysOnly) {
- ActivityManager.TaskDescription rawTd = t.getTaskDescription();
- TaskDescriptionCompat td = new TaskDescriptionCompat(rawTd);
- boolean isLocked = tmpLockedUsers.get(t.getUserId());
- task = new Task(taskKey, td.getPrimaryColor(), td.getBackgroundColor(),
- t.supportsSplitScreenMultiWindow(), isLocked, rawTd, t.getTopActivity());
+ boolean isLocked = tmpLockedUsers.get(taskKey.userId);
+ task = Task.from(taskKey, rawTask, isLocked);
} else {
task = new Task(taskKey);
}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
index 0822e61..4d1d9ef 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
@@ -22,7 +24,6 @@
import android.os.Bundle;
import android.os.Handler;
-import com.android.launcher3.MainThreadExecutor;
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
import com.android.quickstep.util.RemoteAnimationProvider;
@@ -68,7 +69,7 @@
Context context, Handler handler, long duration) {
register();
- Bundle options = animProvider.toActivityOptions(handler, duration).toBundle();
+ Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle();
context.startActivity(intent, options);
}
@@ -92,14 +93,10 @@
private static class Scheduler implements Runnable {
private WeakReference<RecentsActivityTracker> mPendingTracker = new WeakReference<>(null);
- private MainThreadExecutor mMainThreadExecutor;
public synchronized void schedule(RecentsActivityTracker tracker) {
mPendingTracker = new WeakReference<>(tracker);
- if (mMainThreadExecutor == null) {
- mMainThreadExecutor = new MainThreadExecutor();
- }
- mMainThreadExecutor.execute(this);
+ MAIN_EXECUTOR.execute(this);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 9f12484..2e59ed5 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -15,27 +15,31 @@
*/
package com.android.quickstep;
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.createAndStartNewLooper;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.os.Build;
-import android.os.Bundle;
-import android.os.HandlerThread;
+import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Log;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.LauncherAppsCompat.OnAppsChangedCallbackCompat;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.KeyguardManagerCompat;
import com.android.systemui.shared.system.TaskStackChangeListener;
import java.util.ArrayList;
@@ -52,7 +56,7 @@
// We do not need any synchronization for this variable as its only written on UI thread.
public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
- new MainThreadInitializedObject<>(c -> new RecentsModel(c));
+ new MainThreadInitializedObject<>(RecentsModel::new);
private final List<TaskThumbnailChangeListener> mThumbnailChangeListeners = new ArrayList<>();
private final Context mContext;
@@ -65,13 +69,14 @@
private RecentsModel(Context context) {
mContext = context;
- HandlerThread loaderThread = new HandlerThread("TaskThumbnailIconCache",
- Process.THREAD_PRIORITY_BACKGROUND);
- loaderThread.start();
- mTaskList = new RecentTasksList(context);
- mIconCache = new TaskIconCache(context, loaderThread.getLooper());
- mThumbnailCache = new TaskThumbnailCache(context, loaderThread.getLooper());
+ Looper looper =
+ createAndStartNewLooper("TaskThumbnailIconCache", THREAD_PRIORITY_BACKGROUND);
+ mTaskList = new RecentTasksList(MAIN_EXECUTOR,
+ new KeyguardManagerCompat(context), ActivityManagerWrapper.getInstance());
+ mIconCache = new TaskIconCache(context, looper);
+ mThumbnailCache = new TaskThumbnailCache(context, looper);
ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
+ setupPackageListener();
}
public TaskIconCache getIconCache() {
@@ -170,6 +175,7 @@
public void onTaskRemoved(int taskId) {
Task.TaskKey dummyKey = new Task.TaskKey(taskId, 0, null, null, 0, 0);
mThumbnailCache.remove(dummyKey);
+ mIconCache.onTaskRemoved(dummyKey);
}
public void setSystemUiProxy(ISystemUiProxy systemUiProxy) {
@@ -204,6 +210,21 @@
}
}
+ private void setupPackageListener() {
+ LauncherAppsCompat.getInstance(mContext)
+ .addOnAppsChangedCallback(new OnAppsChangedCallbackCompat() {
+ @Override
+ public void onPackageRemoved(String packageName, UserHandle user) {
+ mIconCache.invalidatePackage(packageName);
+ }
+
+ @Override
+ public void onPackageChanged(String packageName, UserHandle user) {
+ mIconCache.invalidatePackage(packageName);
+ }
+ });
+ }
+
public void addThumbnailChangeListener(TaskThumbnailChangeListener listener) {
mThumbnailChangeListeners.add(listener);
}
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 07af9b3..289a129 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -16,6 +16,7 @@
package com.android.quickstep;
import static com.android.launcher3.uioverrides.RecentsUiFactory.GO_LOW_RAM_RECENTS_ENABLED;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.content.ComponentName;
import android.content.Context;
@@ -27,26 +28,25 @@
import android.util.LruCache;
import android.view.accessibility.AccessibilityManager;
-import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.cache.HandlerRunnable;
-import com.android.launcher3.uioverrides.RecentsUiFactory;
import com.android.launcher3.util.Preconditions;
import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.recents.model.TaskKeyLruCache;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import java.util.Map;
import java.util.function.Consumer;
/**
* Manages the caching of task icons and related data.
- * TODO: This class should later be merged into IconCache.
+ * TODO(b/138944598): This class should later be merged into IconCache.
*/
public class TaskIconCache {
private final Handler mBackgroundHandler;
- private final MainThreadExecutor mMainThreadExecutor;
private final AccessibilityManager mAccessibilityManager;
private final NormalizedIconLoader mIconLoader;
@@ -67,7 +67,6 @@
public TaskIconCache(Context context, Looper backgroundLooper) {
mBackgroundHandler = new Handler(backgroundLooper);
- mMainThreadExecutor = new MainThreadExecutor();
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
Resources res = context.getResources();
@@ -103,7 +102,7 @@
// We don't call back to the provided callback in this case
return;
}
- mMainThreadExecutor.execute(() -> {
+ MAIN_EXECUTOR.execute(() -> {
task.icon = icon;
task.titleDescription = contentDescription;
callback.accept(task);
@@ -149,6 +148,21 @@
return label;
}
+
+ void onTaskRemoved(TaskKey taskKey) {
+ mIconCache.remove(taskKey);
+ }
+
+ void invalidatePackage(String packageName) {
+ // TODO(b/138944598): Merge this class into IconCache so we can do this at the base level
+ Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
+ for (ComponentName cn : activityInfoCache.keySet()) {
+ if (cn.getPackageName().equals(packageName)) {
+ mActivityInfoCache.remove(cn);
+ }
+ }
+ }
+
public static abstract class IconLoadRequest extends HandlerRunnable {
IconLoadRequest(Handler handler) {
super(handler, null);
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index 57c5a27..3b50c26 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -15,12 +15,14 @@
*/
package com.android.quickstep;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;
-import com.android.launcher3.MainThreadExecutor;
+
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.cache.HandlerRunnable;
@@ -30,13 +32,13 @@
import com.android.systemui.shared.recents.model.TaskKeyLruCache;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+
import java.util.ArrayList;
import java.util.function.Consumer;
public class TaskThumbnailCache {
private final Handler mBackgroundHandler;
- private final MainThreadExecutor mMainThreadExecutor;
private final int mCacheSize;
private final ThumbnailCache mCache;
@@ -94,7 +96,6 @@
public TaskThumbnailCache(Context context, Looper backgroundLooper) {
mBackgroundHandler = new Handler(backgroundLooper);
- mMainThreadExecutor = new MainThreadExecutor();
mHighResLoadingState = new HighResLoadingState(context);
Resources res = context.getResources();
@@ -168,7 +169,7 @@
// We don't call back to the provided callback in this case
return;
}
- mMainThreadExecutor.execute(() -> {
+ MAIN_EXECUTOR.execute(() -> {
mCache.put(key, thumbnail);
callback.accept(thumbnail);
onEnd();
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 2f411ef..8e5ed1a 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -16,30 +16,33 @@
package com.android.quickstep.logging;
-import android.content.Context;
-import android.content.Intent;
-import android.stats.launcher.nano.LauncherExtension;
-import android.stats.launcher.nano.LauncherTarget;
-
import static android.stats.launcher.nano.Launcher.ALLAPPS;
import static android.stats.launcher.nano.Launcher.HOME;
import static android.stats.launcher.nano.Launcher.LAUNCH_APP;
import static android.stats.launcher.nano.Launcher.LAUNCH_TASK;
+import static android.stats.launcher.nano.Launcher.DISMISS_TASK;
import static android.stats.launcher.nano.Launcher.BACKGROUND;
import static android.stats.launcher.nano.Launcher.OVERVIEW;
+import android.content.Context;
+import android.content.Intent;
+import android.stats.launcher.nano.Launcher;
+import android.stats.launcher.nano.LauncherExtension;
+import android.stats.launcher.nano.LauncherTarget;
+import android.util.Log;
import android.view.View;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogUtils;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.util.ComponentKey;
import com.android.systemui.shared.system.StatsLogCompat;
import com.google.protobuf.nano.MessageNano;
-import androidx.annotation.Nullable;
-
/**
* This method calls the StatsLog hidden method until they are made available public.
*
@@ -51,6 +54,8 @@
public class StatsLogCompatManager extends StatsLogManager {
private static final int SUPPORTED_TARGET_DEPTH = 2;
+ private static final String TAG = "StatsLogCompatManager";
+ private static final boolean DEBUG = false;
public StatsLogCompatManager(Context context) { }
@@ -60,6 +65,9 @@
ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
int srcState = mStateProvider.getCurrentState();
fillInLauncherExtension(v, ext);
+ if (ext.srcTarget[0] != null) {
+ ext.srcTarget[0].item = LauncherTarget.APP_ICON;
+ }
StatsLogCompat.write(LAUNCH_APP, srcState, BACKGROUND /* dstState */,
MessageNano.toByteArray(ext), true);
}
@@ -74,22 +82,154 @@
MessageNano.toByteArray(ext), true);
}
+ @Override
+ public void logTaskDismiss(View v, ComponentKey componentKey) {
+ LauncherExtension ext = new LauncherExtension();
+ ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
+ int srcState = OVERVIEW;
+ fillInLauncherExtension(v, ext);
+ StatsLogCompat.write(DISMISS_TASK, srcState, BACKGROUND /* dstState */,
+ MessageNano.toByteArray(ext), true);
+ }
+
+ @Override
+ public void logSwipeOnContainer(boolean isSwipingToLeft, int pageId) {
+ LauncherExtension ext = new LauncherExtension();
+ ext.srcTarget = new LauncherTarget[1];
+ int srcState = mStateProvider.getCurrentState();
+ fillInLauncherExtensionWithPageId(ext, pageId);
+ int launcherAction = isSwipingToLeft ? Launcher.SWIPE_LEFT : Launcher.SWIPE_RIGHT;
+ StatsLogCompat.write(launcherAction, srcState, srcState,
+ MessageNano.toByteArray(ext), true);
+ }
+
public static boolean fillInLauncherExtension(View v, LauncherExtension extension) {
+ if (DEBUG) {
+ Log.d(TAG, "fillInLauncherExtension");
+ }
+
StatsLogUtils.LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(v);
if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
+ if (DEBUG) {
+ Log.d(TAG, "View or provider is null, or view doesn't have an ItemInfo tag.");
+ }
+
return false;
}
ItemInfo itemInfo = (ItemInfo) v.getTag();
Target child = new Target();
Target parent = new Target();
provider.fillInLogContainerData(v, itemInfo, child, parent);
+ extension.srcTarget[0] = new LauncherTarget();
+ extension.srcTarget[1] = new LauncherTarget();
copy(child, extension.srcTarget[0]);
copy(parent, extension.srcTarget[1]);
return true;
}
+ public static boolean fillInLauncherExtensionWithPageId(LauncherExtension ext, int pageId) {
+ if (DEBUG) {
+ Log.d(TAG, "fillInLauncherExtensionWithPageId, pageId = " + pageId);
+ }
+
+ Target target = new Target();
+ target.pageIndex = pageId;
+ ext.srcTarget[0] = new LauncherTarget();
+ copy(target, ext.srcTarget[0]);
+ return true;
+ }
+
private static void copy(Target src, LauncherTarget dst) {
- // fill in
+ if (DEBUG) {
+ Log.d(TAG, "copy target information from clearcut Target to LauncherTarget.");
+ }
+
+ // Fill in type
+ switch (src.type) {
+ case Target.Type.ITEM:
+ dst.type = LauncherTarget.ITEM_TYPE;
+ break;
+ case Target.Type.CONTROL:
+ dst.type = LauncherTarget.CONTROL_TYPE;
+ break;
+ case Target.Type.CONTAINER:
+ dst.type = LauncherTarget.CONTAINER_TYPE;
+ break;
+ default:
+ dst.type = LauncherTarget.NONE;
+ break;
+ }
+
+ // Fill in item
+ switch (src.itemType) {
+ case ItemType.APP_ICON:
+ dst.item = LauncherTarget.APP_ICON;
+ break;
+ case ItemType.SHORTCUT:
+ dst.item = LauncherTarget.SHORTCUT;
+ break;
+ case ItemType.WIDGET:
+ dst.item = LauncherTarget.WIDGET;
+ break;
+ case ItemType.FOLDER_ICON:
+ dst.item = LauncherTarget.FOLDER_ICON;
+ break;
+ case ItemType.DEEPSHORTCUT:
+ dst.item = LauncherTarget.DEEPSHORTCUT;
+ break;
+ case ItemType.SEARCHBOX:
+ dst.item = LauncherTarget.SEARCHBOX;
+ break;
+ case ItemType.EDITTEXT:
+ dst.item = LauncherTarget.EDITTEXT;
+ break;
+ case ItemType.NOTIFICATION:
+ dst.item = LauncherTarget.NOTIFICATION;
+ break;
+ case ItemType.TASK:
+ dst.item = LauncherTarget.TASK;
+ break;
+ default:
+ dst.item = LauncherTarget.DEFAULT_ITEM;
+ break;
+ }
+
+ // Fill in container
+ switch (src.containerType) {
+ case ContainerType.HOTSEAT:
+ dst.container = LauncherTarget.HOTSEAT;
+ break;
+ case ContainerType.FOLDER:
+ dst.container = LauncherTarget.FOLDER;
+ break;
+ case ContainerType.PREDICTION:
+ dst.container = LauncherTarget.PREDICTION;
+ break;
+ case ContainerType.SEARCHRESULT:
+ dst.container = LauncherTarget.SEARCHRESULT;
+ break;
+ default:
+ dst.container = LauncherTarget.DEFAULT_CONTAINER;
+ break;
+ }
+
+ // Fill in control
+ switch (src.controlType) {
+ case ControlType.UNINSTALL_TARGET:
+ dst.control = LauncherTarget.UNINSTALL;
+ break;
+ case ControlType.REMOVE_TARGET:
+ dst.control = LauncherTarget.REMOVE;
+ break;
+ default:
+ dst.control = LauncherTarget.DEFAULT_CONTROL;
+ break;
+ }
+
+ // Fill in other fields
+ dst.pageId = src.pageIndex;
+ dst.gridX = src.gridX;
+ dst.gridY = src.gridY;
}
@Override
diff --git a/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java b/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
index 4a11601..9ca7f23 100644
--- a/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
+++ b/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
@@ -20,10 +20,10 @@
import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CANCEL_TARGET;
-import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
import static com.android.systemui.shared.system.LauncherEventUtil.DISMISS;
import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_QUICK_SCRUB_ONBOARDING_TIP;
import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_SWIPE_UP_ONBOARDING_TIP;
+import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.userevent.nano.LauncherLogProto;
diff --git a/quickstep/src/com/android/quickstep/util/AssistantUtilities.java b/quickstep/src/com/android/quickstep/util/AssistantUtilities.java
new file mode 100644
index 0000000..552db1f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/AssistantUtilities.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 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.quickstep.util;
+
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT;
+
+import android.annotation.TargetApi;
+import android.app.TaskInfo;
+import android.content.Intent;
+import android.os.Build;
+
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.TaskInfoCompat;
+
+/**
+ * Utility class for interacting with the Assistant.
+ */
+@TargetApi(Build.VERSION_CODES.Q)
+public final class AssistantUtilities {
+
+ /** Returns true if an Assistant activity that is excluded from recents is running. */
+ public static boolean isExcludedAssistantRunning() {
+ return isExcludedAssistant(ActivityManagerWrapper.getInstance().getRunningTask());
+ }
+
+ /** Returns true if the given task holds an Assistant activity that is excluded from recents. */
+ public static boolean isExcludedAssistant(TaskInfo info) {
+ return info != null
+ && TaskInfoCompat.getActivityType(info) == ACTIVITY_TYPE_ASSISTANT
+ && (info.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
+ }
+
+ private AssistantUtilities() {}
+}
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index 050bdff..2e118b4 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -26,7 +26,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.quickstep.SysUINavigationMode;
import java.lang.annotation.Retention;
@@ -39,12 +39,27 @@
@IntDef({MULTI_WINDOW_STRATEGY_HALF_SCREEN, MULTI_WINDOW_STRATEGY_DEVICE_PROFILE})
private @interface MultiWindowStrategy {}
+ /**
+ * The height for the swipe up motion
+ */
+ public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) {
+ float swipeHeight = dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
+ if (SysUINavigationMode.getMode(context) == SysUINavigationMode.Mode.NO_BUTTON) {
+ swipeHeight -= dp.getInsets().bottom;
+ }
+ return swipeHeight;
+ }
+
public static void calculateLauncherTaskSize(Context context, DeviceProfile dp, Rect outRect) {
float extraSpace;
if (dp.isVerticalBarLayout()) {
extraSpace = 0;
} else {
- extraSpace = dp.hotseatBarSizePx + dp.verticalDragHandleSizePx;
+ Resources res = context.getResources();
+
+ extraSpace = getDefaultSwipeHeight(context, dp) + dp.verticalDragHandleSizePx
+ + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
+ + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
}
calculateTaskSize(context, dp, extraSpace, MULTI_WINDOW_STRATEGY_HALF_SCREEN, outRect);
}
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index a7e6d74..4503a43 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -17,6 +17,7 @@
import android.animation.AnimatorSet;
import android.app.ActivityOptions;
+import android.content.Context;
import android.os.Handler;
import com.android.launcher3.LauncherAnimationRunner;
@@ -32,14 +33,14 @@
AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targets);
- default ActivityOptions toActivityOptions(Handler handler, long duration) {
+ default ActivityOptions toActivityOptions(Handler handler, long duration, Context context) {
LauncherAnimationRunner runner = new LauncherAnimationRunner(handler,
false /* startAtFrontOfQueue */) {
@Override
public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
AnimationResult result) {
- result.setAnimation(createWindowAnimation(targetCompats));
+ result.setAnimation(createWindowAnimation(targetCompats), context);
}
};
return ActivityOptionsCompat.makeRemoteAnimation(
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index 63c8023..3320dae 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.QUICK_SWITCH;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -35,15 +36,18 @@
import android.view.animation.Interpolator;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.QuickstepAppTransitionManagerImpl;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.uioverrides.states.OverviewState;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ScrimView;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.ShelfPeekAnim;
/**
* Scrim used for all-apps and shelf in Overview
@@ -74,6 +78,9 @@
private int mMidAlpha;
private float mMidProgress;
+ // The progress at which the drag handle starts moving up with the shelf.
+ private float mDragHandleProgress;
+
private Interpolator mBeforeMidProgressColorInterpolator = ACCEL;
private Interpolator mAfterMidProgressColorInterpolator = ACCEL;
@@ -95,7 +102,7 @@
public ShelfScrimView(Context context, AttributeSet attrs) {
super(context, attrs);
- mMaxScrimAlpha = Math.round(OVERVIEW.getWorkspaceScrimAlpha(mLauncher) * 255);
+ mMaxScrimAlpha = Math.round(OVERVIEW.getOverviewScrimAlpha(mLauncher) * 255);
mEndAlpha = Color.alpha(mEndScrim);
mRadius = BOTTOM_CORNER_RADIUS_RATIO * Themes.getDialogCornerRadius(context);
@@ -150,15 +157,18 @@
if ((OVERVIEW.getVisibleElements(mLauncher) & ALL_APPS_HEADER_EXTRA) == 0) {
mMidProgress = 1;
+ mDragHandleProgress = 1;
mMidAlpha = 0;
} else {
- mMidAlpha = Themes.getAttrInteger(getContext(), R.attr.allAppsInterimScrimAlpha);
+ Context context = getContext();
+ mMidAlpha = Themes.getAttrInteger(context, R.attr.allAppsInterimScrimAlpha);
+ mMidProgress = OVERVIEW.getVerticalProgress(mLauncher);
Rect hotseatPadding = dp.getHotseatLayoutPadding();
int hotseatSize = dp.hotseatBarSizePx + dp.getInsets().bottom
- - hotseatPadding.bottom - hotseatPadding.top;
- float arrowTop = Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(dp));
- mMidProgress = 1 - (arrowTop / mShiftRange);
-
+ + hotseatPadding.bottom + hotseatPadding.top;
+ float dragHandleTop =
+ Math.min(hotseatSize, LayoutUtils.getDefaultSwipeHeight(context, dp));
+ mDragHandleProgress = 1 - (dragHandleTop / mShiftRange);
}
mTopOffset = dp.getInsets().top - mShelfOffset;
mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset;
@@ -187,8 +197,12 @@
if (mProgress >= 1) {
mRemainingScreenColor = 0;
mShelfColor = 0;
+ ShelfPeekAnim shelfPeekAnim = ((QuickstepAppTransitionManagerImpl)
+ mLauncher.getAppTransitionManager()).getShelfPeekAnim();
+ LauncherState state = mLauncher.getStateManager().getState();
if (mSysUINavigationMode == Mode.NO_BUTTON
- && mLauncher.getStateManager().getState() == BACKGROUND_APP) {
+ && (state == BACKGROUND_APP || state == QUICK_SWITCH)
+ && shelfPeekAnim.isPeeking()) {
// Show the shelf background when peeking during swipe up.
mShelfColor = setColorAlphaBound(mEndScrim, mMidAlpha);
}
@@ -199,8 +213,6 @@
mProgress, mMidProgress, 1, mMidAlpha, 0, mBeforeMidProgressColorInterpolator));
mShelfColor = setColorAlphaBound(mEndScrim, alpha);
} else {
- mDragHandleOffset += mShiftRange * (mMidProgress - mProgress);
-
// Note that these ranges and interpolators are inverted because progress goes 1 to 0.
int alpha = Math.round(
Utilities.mapToRange(mProgress, (float) 0, mMidProgress, (float) mEndAlpha,
@@ -212,6 +224,10 @@
(float) 0, LINEAR));
mRemainingScreenColor = setColorAlphaBound(mScrimColor, remainingScrimAlpha);
}
+
+ if (mProgress < mDragHandleProgress) {
+ mDragHandleOffset += mShiftRange * (mDragHandleProgress - mProgress);
+ }
}
@Override
diff --git a/quickstep/tests/OWNERS b/quickstep/tests/OWNERS
index 046d871..02e8ebc 100644
--- a/quickstep/tests/OWNERS
+++ b/quickstep/tests/OWNERS
@@ -1 +1,4 @@
vadimt@google.com
+sunnygoyal@google.com
+winsonc@google.com
+hyunyoungs@google.com
diff --git a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
index d9fcf4d..7801775 100644
--- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
+++ b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
@@ -88,7 +88,7 @@
*/
@Test
public void testPredictionExistsInAllApps() {
- mActivityMonitor.startLauncher();
+ mDevice.pressHome();
mLauncher.pressHome().switchToAllApps();
// Dispatch an update
@@ -103,7 +103,7 @@
*/
@Test
public void testPredictionsDeferredUntilHome() {
- mActivityMonitor.startLauncher();
+ mDevice.pressHome();
sendPredictionUpdate(mSampleApp1, mSampleApp2);
mLauncher.pressHome().switchToAllApps();
waitForLauncherCondition("Predictions were not updated in loading state",
@@ -120,7 +120,7 @@
@Test
public void testPredictionsDisabled() {
- mActivityMonitor.startLauncher();
+ mDevice.pressHome();
sendPredictionUpdate();
mLauncher.pressHome().switchToAllApps();
@@ -150,10 +150,10 @@
List<AppTarget> targets = new ArrayList<>(activities.length);
for (LauncherActivityInfo info : activities) {
ComponentName cn = info.getComponentName();
- AppTarget target =
- new AppTarget.Builder(new AppTargetId("app:" + cn), cn.getPackageName(), info.getUser())
- .setClassName(cn.getClassName())
- .build();
+ AppTarget target = new AppTarget.Builder(
+ new AppTargetId("app:" + cn), cn.getPackageName(), info.getUser())
+ .setClassName(cn.getClassName())
+ .build();
targets.add(target);
}
mCallback.onTargetsAvailable(targets);
diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index 0c5a6f5..a7c33a9 100644
--- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -51,7 +51,7 @@
mLauncher.pressHome();
final DigitalWellBeingToast toast = getToast();
- assertTrue("Toast is not visible", toast.hasLimit());
+ waitForLauncherCondition("Toast is not visible", launcher -> toast.hasLimit());
assertEquals("Toast text: ", "5 minutes left today", toast.getText());
// Unset time limit for app.
@@ -69,10 +69,9 @@
private DigitalWellBeingToast getToast() {
executeOnLauncher(launcher -> launcher.getStateManager().goToState(OVERVIEW));
waitForState("Launcher internal state didn't switch to Overview", OVERVIEW);
- waitForLauncherCondition("No latest task", launcher -> getLatestTask(launcher) != null);
+ final TaskView task = getOnceNotNull("No latest task", launcher -> getLatestTask(launcher));
return getFromLauncher(launcher -> {
- final TaskView task = getLatestTask(launcher);
assertTrue("Latest task is not Calculator",
CALCULATOR_PACKAGE.equals(task.getTask().getTopComponent().getPackageName()));
return task.getDigitalWellBeingToast();
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index e5f949b..8c11c1c 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -42,6 +42,7 @@
import androidx.test.uiautomator.Until;
import com.android.launcher3.tapl.LauncherInstrumentation;
+import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.testcomponent.TestCommandReceiver;
import com.android.launcher3.util.rule.FailureWatcher;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
@@ -78,7 +79,7 @@
Context context = instrumentation.getContext();
mDevice = UiDevice.getInstance(instrumentation);
mDevice.setOrientationNatural();
- mLauncher = new LauncherInstrumentation(instrumentation);
+ mLauncher = new LauncherInstrumentation();
mOrderSensitiveRules = RuleChain.
outerRule(new NavigationModeSwitchRule(mLauncher)).
@@ -103,6 +104,11 @@
}
}
};
+ if (TestHelpers.isInLauncherProcess()) {
+ mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand(
+ TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()).
+ getString("result"));
+ }
}
@NavigationModeSwitch(mode = THREE_BUTTON)
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 3b35c86..c2197ab 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -27,15 +27,16 @@
import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_GESTURAL_OVERLAY;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.util.Log;
import androidx.test.uiautomator.UiDevice;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.tapl.TestHelpers;
+import com.android.launcher3.util.rule.FailureWatcher;
import com.android.systemui.shared.system.QuickStepContract;
-import org.junit.Assert;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
@@ -78,8 +79,17 @@
description.getAnnotation(NavigationModeSwitch.class) != null) {
Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode();
return new Statement() {
+ private void assertTrue(String message, boolean condition) {
+ if(!condition) {
+ final AssertionError assertionError = new AssertionError(message);
+ FailureWatcher.onError(mLauncher.getDevice(), description, assertionError);
+ throw assertionError;
+ }
+ }
+
@Override
public void evaluate() throws Throwable {
+ mLauncher.enableDebugTracing();
final Context context = getInstrumentation().getContext();
final int currentInteractionMode =
LauncherInstrumentation.getCurrentInteractionMode(context);
@@ -101,35 +111,55 @@
if (mode == THREE_BUTTON || mode == ALL) {
evaluateWithThreeButtons();
}
+ } catch (Exception e) {
+ Log.e(TAG, "Exception", e);
+ throw e;
} finally {
- setActiveOverlay(prevOverlayPkg, originalMode);
+ assertTrue("Couldn't set overlay",
+ setActiveOverlay(prevOverlayPkg, originalMode));
}
}
- public void evaluateWithoutChangingSetting(Statement base) throws Throwable {
- base.evaluate();
- }
-
private void evaluateWithThreeButtons() throws Throwable {
- setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY,
- LauncherInstrumentation.NavigationModel.THREE_BUTTON);
- evaluateWithoutChangingSetting(base);
+ if (setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY,
+ LauncherInstrumentation.NavigationModel.THREE_BUTTON)) {
+ base.evaluate();
+ }
}
private void evaluateWithTwoButtons() throws Throwable {
- setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY,
- LauncherInstrumentation.NavigationModel.TWO_BUTTON);
- base.evaluate();
+ if (setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY,
+ LauncherInstrumentation.NavigationModel.TWO_BUTTON)) {
+ base.evaluate();
+ }
}
private void evaluateWithZeroButtons() throws Throwable {
- setActiveOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY,
- LauncherInstrumentation.NavigationModel.ZERO_BUTTON);
- base.evaluate();
+ if (setActiveOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY,
+ LauncherInstrumentation.NavigationModel.ZERO_BUTTON)) {
+ base.evaluate();
+ }
}
- private void setActiveOverlay(String overlayPackage,
+ private boolean packageExists(String packageName) {
+ try {
+ PackageManager pm = getInstrumentation().getContext().getPackageManager();
+ if (pm.getApplicationInfo(packageName, 0 /* flags */) == null) {
+ return false;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ return true;
+ }
+
+ private boolean setActiveOverlay(String overlayPackage,
LauncherInstrumentation.NavigationModel expectedMode) throws Exception {
+ if (!packageExists(overlayPackage)) {
+ Log.d(TAG, "setActiveOverlay: " + overlayPackage + " pkg does not exist");
+ return false;
+ }
+
setOverlayPackageEnabled(NAV_BAR_MODE_3BUTTON_OVERLAY,
overlayPackage == NAV_BAR_MODE_3BUTTON_OVERLAY);
setOverlayPackageEnabled(NAV_BAR_MODE_2BUTTON_OVERLAY,
@@ -151,10 +181,10 @@
SysUINavigationMode.INSTANCE.get(targetContext);
targetContext.getMainExecutor().execute(() ->
sysUINavigationMode.addModeChangeListener(listener));
- latch.await(10, TimeUnit.SECONDS);
+ latch.await(60, TimeUnit.SECONDS);
targetContext.getMainExecutor().execute(() ->
sysUINavigationMode.removeModeChangeListener(listener));
- Assert.assertTrue("Navigation mode didn't change to " + expectedMode,
+ assertTrue("Navigation mode didn't change to " + expectedMode,
currentSysUiNavigationMode() == expectedMode);
}
@@ -162,7 +192,7 @@
if (mLauncher.getNavigationModel() == expectedMode) break;
Thread.sleep(100);
}
- Assert.assertTrue("Couldn't switch to " + overlayPackage,
+ assertTrue("Couldn't switch to " + overlayPackage,
mLauncher.getNavigationModel() == expectedMode);
for (int i = 0; i != 100; ++i) {
@@ -170,9 +200,10 @@
Thread.sleep(100);
}
final String error = mLauncher.getNavigationModeMismatchError();
- Assert.assertTrue("Switching nav mode: " + error, error == null);
+ assertTrue("Switching nav mode: " + error, error == null);
Thread.sleep(5000);
+ return true;
}
private void setOverlayPackageEnabled(String overlayPackage, boolean enable)
diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
new file mode 100644
index 0000000..34eb7f8
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2019 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.quickstep;
+
+import static junit.framework.TestCase.assertNull;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.util.LooperExecutor;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.KeyguardManagerCompat;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+public class RecentTasksListTest {
+
+ private ActivityManagerWrapper mockActivityManagerWrapper;
+
+ // Class under test
+ private RecentTasksList mRecentTasksList;
+
+ @Before
+ public void setup() {
+ LooperExecutor mockMainThreadExecutor = mock(LooperExecutor.class);
+ KeyguardManagerCompat mockKeyguardManagerCompat = mock(KeyguardManagerCompat.class);
+ mockActivityManagerWrapper = mock(ActivityManagerWrapper.class);
+ mRecentTasksList = new RecentTasksList(mockMainThreadExecutor, mockKeyguardManagerCompat,
+ mockActivityManagerWrapper);
+ }
+
+ @Test
+ public void onTaskRemoved_reloadsAllTasks() {
+ mRecentTasksList.onTaskRemoved(0);
+ verify(mockActivityManagerWrapper, times(1))
+ .getRecentTasks(anyInt(), anyInt());
+ }
+
+ @Test
+ public void onTaskStackChanged_doesNotFetchTasks() {
+ mRecentTasksList.onTaskStackChanged();
+ verify(mockActivityManagerWrapper, times(0))
+ .getRecentTasks(anyInt(), anyInt());
+ }
+
+ @Test
+ public void loadTasksInBackground_onlyKeys_noValidTaskDescription() {
+ ActivityManager.RecentTaskInfo recentTaskInfo = new ActivityManager.RecentTaskInfo();
+ when(mockActivityManagerWrapper.getRecentTasks(anyInt(), anyInt()))
+ .thenReturn(Collections.singletonList(recentTaskInfo));
+
+ List<Task> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, true);
+
+ assertEquals(1, taskList.size());
+ assertNull(taskList.get(0).taskDescription.getLabel());
+ }
+
+ @Test
+ public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() {
+ String taskDescription = "Wheeee!";
+ ActivityManager.RecentTaskInfo recentTaskInfo = new ActivityManager.RecentTaskInfo();
+ recentTaskInfo.taskDescription = new ActivityManager.TaskDescription(taskDescription);
+ when(mockActivityManagerWrapper.getRecentTasks(anyInt(), anyInt()))
+ .thenReturn(Collections.singletonList(recentTaskInfo));
+
+ List<Task> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, false);
+
+ assertEquals(1, taskList.size());
+ assertEquals(taskDescription, taskList.get(0).taskDescription.getLabel());
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 9e3bf2f..8cd3bb6 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -35,6 +35,7 @@
import com.android.launcher3.tapl.AllApps;
import com.android.launcher3.tapl.AllAppsFromOverview;
import com.android.launcher3.tapl.Background;
+import com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel;
import com.android.launcher3.tapl.Overview;
import com.android.launcher3.tapl.OverviewTask;
import com.android.launcher3.tapl.TestHelpers;
@@ -208,19 +209,24 @@
@Test
@NavigationModeSwitch
-// @PortraitLandscape
+ @PortraitLandscape
public void testBackground() throws Exception {
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+ final Background background = getAndAssertBackground();
+
+ assertNotNull("Background.switchToOverview() returned null", background.switchToOverview());
+ assertTrue("Launcher internal state didn't switch to Overview",
+ isInState(LauncherState.OVERVIEW));
+ }
+
+ private Background getAndAssertBackground() {
final Background background = mLauncher.getBackground();
assertNotNull("Launcher.getBackground() returned null", background);
executeOnLauncher(launcher -> assertTrue(
"Launcher activity is the top activity; expecting another activity to be the top "
+ "one",
isInBackground(launcher)));
-
- assertNotNull("Background.switchToOverview() returned null", background.switchToOverview());
- assertTrue("Launcher internal state didn't switch to Overview",
- isInState(LauncherState.OVERVIEW));
+ return background;
}
@Test
@@ -238,4 +244,47 @@
assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL));
assertNotNull("getHome returned null", mLauncher.getWorkspace());
}
+
+ @Test
+ @NavigationModeSwitch
+ @PortraitLandscape
+ public void testQuickSwitchFromApp() throws Exception {
+ startTestActivity(2);
+ startTestActivity(3);
+ startTestActivity(4);
+
+ Background background = getAndAssertBackground();
+ background.quickSwitchToPreviousApp();
+ assertTrue("The first app we should have quick switched to is not running",
+ isTestActivityRunning(3));
+
+ background = getAndAssertBackground();
+ background.quickSwitchToPreviousApp();
+ if (mLauncher.getNavigationModel() == NavigationModel.THREE_BUTTON) {
+ // 3-button mode toggles between 2 apps, rather than going back further.
+ assertTrue("Second quick switch should have returned to the first app.",
+ isTestActivityRunning(4));
+ } else {
+ assertTrue("The second app we should have quick switched to is not running",
+ isTestActivityRunning(2));
+ }
+ getAndAssertBackground();
+ }
+
+ private boolean isTestActivityRunning(int activityNumber) {
+ return mDevice.wait(Until.hasObject(By.pkg(getAppPackageName())
+ .text("TestActivity" + activityNumber)),
+ DEFAULT_UI_TIMEOUT);
+ }
+
+ @Test
+ @NavigationModeSwitch
+ @PortraitLandscape
+ public void testQuickSwitchFromHome() throws Exception {
+ startTestActivity(2);
+ mLauncher.pressHome().quickSwitchToPreviousApp();
+ assertTrue("The most recent task is not running after quick switching from home",
+ isTestActivityRunning(2));
+ getAndAssertBackground();
+ }
}
diff --git a/res/layout/folder_application.xml b/res/layout/folder_application.xml
index c156e11..32a5419 100644
--- a/res/layout/folder_application.xml
+++ b/res/layout/folder_application.xml
@@ -20,4 +20,5 @@
style="@style/BaseIcon"
android:textColor="?attr/folderTextColor"
android:includeFontPadding="false"
+ android:hapticFeedbackEnabled="false"
launcher:iconDisplay="folder" />
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index ca59afa..ba16dd3 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Werk"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Program is nie geïnstalleer nie."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Program is nie beskikbaar nie"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index d942e9e..0396df6 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"ስራ"</string>
<string name="activity_not_found" msgid="8071924732094499514">"መተግበሪያ አልተጫነም።"</string>
<string name="activity_not_available" msgid="7456344436509528827">"መተግበሪያ አይገኝም"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index a80ecb0..2fbdae1 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"العمل"</string>
<string name="activity_not_found" msgid="8071924732094499514">"لم يتم تثبيت التطبيق."</string>
<string name="activity_not_available" msgid="7456344436509528827">"التطبيق ليس متاحًا"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 2984603..221921f 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"কৰ্মস্থান"</string>
<string name="activity_not_found" msgid="8071924732094499514">"এপটো ইনষ্টল কৰা নহ\'ল।"</string>
<string name="activity_not_available" msgid="7456344436509528827">"এপটো নাই"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 5528a19..7c1ce84 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"İş"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Tətbiq quraşdırılmayıb."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Tətbiq əlçatmazdır"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 883003c..e6fe3bd 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Aplikacija nije instalirana."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Aplikacija nije dostupna"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index b4cf913..2ccd34f 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Працоўная"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Праграма не ўсталявана."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Праграма недаступная"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 408f205..f7d1d0f 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Работа"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Приложението не е инсталирано."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Приложението не е налично"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 775885c..0a72632 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"কাজ"</string>
<string name="activity_not_found" msgid="8071924732094499514">"অ্যাপ্লিকেশান ইনস্টল করা নেই৷"</string>
<string name="activity_not_available" msgid="7456344436509528827">"অ্যাপ্লিকেশান অনুপলব্ধ"</string>
@@ -93,7 +92,7 @@
<string name="title_change_settings" msgid="1376365968844349552">"সেটিংস পরিবর্তন করুন"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"বিজ্ঞপ্তির ডট দেখুন"</string>
<string name="auto_add_shortcuts_label" msgid="8222286205987725611">"হোম স্ক্রিনে আইকন যোগ করুন"</string>
- <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"নতুন অ্যাপ্লিকেশানগুলির জন্যে"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"নতুন অ্যাপের জন্য"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"অজানা"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"সরান"</string>
<string name="abandoned_search" msgid="891119232568284442">"সার্চ"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 5650457..65ad91e 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Posao"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Aplikacija nije instalirana."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Aplikacija nije dostupna"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index c0859d3..4ef9ec3 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Feina"</string>
<string name="activity_not_found" msgid="8071924732094499514">"L\'aplicació no s\'ha instal·lat."</string>
<string name="activity_not_available" msgid="7456344436509528827">"L\'aplicació no està disponible."</string>
@@ -88,7 +87,7 @@
<string name="notification_dots_title" msgid="9062440428204120317">"Punts de notificació"</string>
<string name="notification_dots_desc_on" msgid="1679848116452218908">"Activats"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"Desactivats"</string>
- <string name="title_missing_notification_access" msgid="7503287056163941064">"Cal que tingui accés a les notificacions"</string>
+ <string name="title_missing_notification_access" msgid="7503287056163941064">"Cal accés a les notificacions"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"Per veure els punts de notificació, activa les notificacions de l\'aplicació <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="title_change_settings" msgid="1376365968844349552">"Canvia la configuració"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"Mostra els punts de notificació"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 692b57d..57c5072 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Práce"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Aplikace není nainstalována."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Aplikace není k dispozici."</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index dc17516..9097ffd 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Arbejde"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Appen er ikke installeret."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Appen er ikke tilgængelig"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index a345bab..1602a53 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Arbeit"</string>
<string name="activity_not_found" msgid="8071924732094499514">"App ist nicht installiert."</string>
<string name="activity_not_available" msgid="7456344436509528827">"App nicht verfügbar"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 2363e61..d80e905 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Εργασία"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Η εφαρμογή δεν έχει εγκατασταθεί."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Η εφαρμογή δεν είναι διαθέσιμη"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 0c48b5d..7adc218 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
<string name="activity_not_found" msgid="8071924732094499514">"App isn\'t installed."</string>
<string name="activity_not_available" msgid="7456344436509528827">"App isn\'t available"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..7adc218
--- /dev/null
+++ b/res/values-en-rCA/strings.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"App isn\'t installed."</string>
+ <string name="activity_not_available" msgid="7456344436509528827">"App isn\'t available"</string>
+ <string name="safemode_shortcut_error" msgid="9160126848219158407">"Downloaded app disabled in Safe mode"</string>
+ <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
+ <string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
+ <string name="home_screen" msgid="806512411299847073">"Home screen"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Customised actions"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch & hold to pick up a widget."</string>
+ <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Double-tap & hold to pick up a widget or use customised actions."</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d wide by %2$d high"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Touch & hold to place manually"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Add automatically"</string>
+ <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Search apps"</string>
+ <string name="all_apps_loading_message" msgid="5813968043155271636">"Loading apps…"</string>
+ <string name="all_apps_no_search_results" msgid="3200346862396363786">"No apps found matching \'<xliff:g id="QUERY">%1$s</xliff:g>\'"</string>
+ <string name="all_apps_search_market_message" msgid="1366263386197059176">"Search for more apps"</string>
+ <string name="label_application" msgid="8531721983832654978">"App"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
+ <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Touch & hold to pick up a shortcut."</string>
+ <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Double-tap & hold to pick up a shortcut or use custom actions."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
+ <string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
+ <string name="all_apps_button_label" msgid="8130441508702294465">"Apps list"</string>
+ <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Personal apps list"</string>
+ <string name="all_apps_button_work_label" msgid="7270707118948892488">"Work apps list"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Home"</string>
+ <string name="remove_drop_target_label" msgid="7812859488053230776">"Remove"</string>
+ <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstall"</string>
+ <string name="app_info_drop_target_label" msgid="692894985365717661">"App info"</string>
+ <string name="install_drop_target_label" msgid="2539096853673231757">"Install"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"install shortcuts"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Allows an app to add shortcuts without user intervention."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"read Home settings and shortcuts"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Allows the app to read the settings and shortcuts in Home."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"write Home settings and shortcuts"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Allows the app to change the settings and shortcuts in Home."</string>
+ <string name="msg_no_phone_permission" msgid="9208659281529857371">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not allowed to make phone calls"</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problem loading widget"</string>
+ <string name="gadget_setup_text" msgid="8274003207686040488">"Setup"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"This is a system app and can\'t be uninstalled."</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Unnamed Folder"</string>
+ <string name="disabled_app_label" msgid="6673129024321402780">"Disabled <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <plurals name="dotted_app_label" formatted="false" msgid="5194538107138265416">
+ <item quantity="other"><xliff:g id="APP_NAME_2">%1$s</xliff:g>, has <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> notifications</item>
+ <item quantity="one"><xliff:g id="APP_NAME_0">%1$s</xliff:g>, has <xliff:g id="NOTIFICATION_COUNT_1">%2$d</xliff:g> notification</item>
+ </plurals>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d of %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Home screen %1$d of %2$d"</string>
+ <string name="workspace_new_page" msgid="257366611030256142">"New home screen page"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Folder opened, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="4625795376335528256">"Tap to close folder"</string>
+ <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tap to save rename"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Folder closed"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
+ <string name="styles_wallpaper_button_text" msgid="4342122323125579619">"Styles & wallpapers"</string>
+ <string name="settings_button_text" msgid="8873672322605444408">"Home settings"</string>
+ <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Disabled by your admin"</string>
+ <string name="allow_rotation_title" msgid="7728578836261442095">"Allow Home screen rotation"</string>
+ <string name="allow_rotation_desc" msgid="8662546029078692509">"When phone is rotated"</string>
+ <string name="notification_dots_title" msgid="9062440428204120317">"Notification dots"</string>
+ <string name="notification_dots_desc_on" msgid="1679848116452218908">"On"</string>
+ <string name="notification_dots_desc_off" msgid="1760796511504341095">"Off"</string>
+ <string name="title_missing_notification_access" msgid="7503287056163941064">"Notification access needed"</string>
+ <string name="msg_missing_notification_access" msgid="281113995110910548">"To show Notification Dots, turn on app notifications for <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="title_change_settings" msgid="1376365968844349552">"Change settings"</string>
+ <string name="notification_dots_service_title" msgid="4284221181793592871">"Show notification dots"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Add icon to Home screen"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For new apps"</string>
+ <string name="package_state_unknown" msgid="7592128424511031410">"Unknown"</string>
+ <string name="abandoned_clean_this" msgid="7610119707847920412">"Remove"</string>
+ <string name="abandoned_search" msgid="891119232568284442">"Search"</string>
+ <string name="abandoned_promises_title" msgid="7096178467971716750">"This app is not installed"</string>
+ <string name="abandoned_promise_explanation" msgid="3990027586878167529">"The app for this icon isn\'t installed. You can remove it, or search for the app and install it manually."</string>
+ <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
+ <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widgets"</string>
+ <string name="widgets_list" msgid="796804551140113767">"Widgets list"</string>
+ <string name="widgets_list_closed" msgid="6141506579418771922">"Widgets list closed"</string>
+ <string name="action_add_to_workspace" msgid="8902165848117513641">"Add to Home screen"</string>
+ <string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
+ <string name="item_added_to_workspace" msgid="4211073925752213539">"Item added to home screen"</string>
+ <string name="item_removed" msgid="851119963877842327">"Item removed"</string>
+ <string name="undo" msgid="4151576204245173321">"Undo"</string>
+ <string name="action_move" msgid="4339390619886385032">"Move item"</string>
+ <string name="move_to_empty_cell" msgid="2833711483015685619">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+ <string name="move_to_position" msgid="6750008980455459790">"Move to position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
+ <string name="move_to_hotseat_position" msgid="6295412897075147808">"Move to favourites position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
+ <string name="item_moved" msgid="4606538322571412879">"Item moved"</string>
+ <string name="add_to_folder" msgid="9040534766770853243">"Add to folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="add_to_folder_with_app" msgid="4534929978967147231">"Add to folder with <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="added_to_folder" msgid="4793259502305558003">"Item added to folder"</string>
+ <string name="create_folder_with" msgid="4050141361160214248">"Create folder with: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_created" msgid="6409794597405184510">"Folder created"</string>
+ <string name="action_move_to_workspace" msgid="1603837886334246317">"Move to Home screen"</string>
+ <string name="action_resize" msgid="1802976324781771067">"Re-size"</string>
+ <string name="action_increase_width" msgid="8773715375078513326">"Increase width"</string>
+ <string name="action_increase_height" msgid="459390020612501122">"Increase height"</string>
+ <string name="action_decrease_width" msgid="1374549771083094654">"Decrease width"</string>
+ <string name="action_decrease_height" msgid="282377193880900022">"Decrease height"</string>
+ <string name="widget_resized" msgid="9130327887929620">"Widget re-sized to width <xliff:g id="NUMBER_0">%1$s</xliff:g> height <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+ <string name="action_deep_shortcut" msgid="2864038805849372848">"Short cuts"</string>
+ <string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"Shortcuts and notifications"</string>
+ <string name="action_dismiss_notification" msgid="5909461085055959187">"Dismiss"</string>
+ <string name="notification_dismissed" msgid="6002233469409822874">"Notification dismissed"</string>
+ <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personal"</string>
+ <string name="all_apps_work_tab" msgid="4884822796154055118">"Work"</string>
+ <string name="work_profile_toggle_label" msgid="3081029915775481146">"Work profile"</string>
+ <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Find work apps here"</string>
+ <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Each work app has a badge and is kept secure by your organisation. Move apps to your Home screen for easier access."</string>
+ <string name="work_mode_on_label" msgid="4781128097185272916">"Managed by your organisation"</string>
+ <string name="work_mode_off_label" msgid="3194894777601421047">"Notifications and apps are off"</string>
+ <string name="bottom_work_tab_user_education_close_button" msgid="4224492243977802135">"Close"</string>
+ <string name="bottom_work_tab_user_education_closed" msgid="1098340939861869465">"Closed"</string>
+ <string name="remote_action_failed" msgid="1383965239183576790">"Failed: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 0c48b5d..7adc218 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
<string name="activity_not_found" msgid="8071924732094499514">"App isn\'t installed."</string>
<string name="activity_not_available" msgid="7456344436509528827">"App isn\'t available"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 0c48b5d..7adc218 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
<string name="activity_not_found" msgid="8071924732094499514">"App isn\'t installed."</string>
<string name="activity_not_available" msgid="7456344436509528827">"App isn\'t available"</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..c741482
--- /dev/null
+++ b/res/values-en-rXC/strings.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
+ <string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"App isn\'t installed."</string>
+ <string name="activity_not_available" msgid="7456344436509528827">"App isn\'t available"</string>
+ <string name="safemode_shortcut_error" msgid="9160126848219158407">"Downloaded app disabled in Safe mode"</string>
+ <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
+ <string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
+ <string name="home_screen" msgid="806512411299847073">"Home screen"</string>
+ <string name="custom_actions" msgid="3747508247759093328">"Custom actions"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch & hold to pick up a widget."</string>
+ <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Double-tap & hold to pick up a widget or use custom actions."</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d wide by %2$d high"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Touch & hold to place manually"</string>
+ <string name="place_automatically" msgid="8064208734425456485">"Add automatically"</string>
+ <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Search apps"</string>
+ <string name="all_apps_loading_message" msgid="5813968043155271636">"Loading apps…"</string>
+ <string name="all_apps_no_search_results" msgid="3200346862396363786">"No apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+ <string name="all_apps_search_market_message" msgid="1366263386197059176">"Search for more apps"</string>
+ <string name="label_application" msgid="8531721983832654978">"App"</string>
+ <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
+ <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Touch & hold to pick up a shortcut."</string>
+ <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Double-tap & hold to pick up a shortcut or use custom actions."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
+ <string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favorites tray"</string>
+ <string name="all_apps_button_label" msgid="8130441508702294465">"Apps list"</string>
+ <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Personal apps list"</string>
+ <string name="all_apps_button_work_label" msgid="7270707118948892488">"Work apps list"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Home"</string>
+ <string name="remove_drop_target_label" msgid="7812859488053230776">"Remove"</string>
+ <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstall"</string>
+ <string name="app_info_drop_target_label" msgid="692894985365717661">"App info"</string>
+ <string name="install_drop_target_label" msgid="2539096853673231757">"Install"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"install shortcuts"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Allows an app to add shortcuts without user intervention."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"read Home settings and shortcuts"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Allows the app to read the settings and shortcuts in Home."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"write Home settings and shortcuts"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Allows the app to change the settings and shortcuts in Home."</string>
+ <string name="msg_no_phone_permission" msgid="9208659281529857371">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not allowed to make phone calls"</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problem loading widget"</string>
+ <string name="gadget_setup_text" msgid="8274003207686040488">"Setup"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"This is a system app and can\'t be uninstalled."</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Unnamed Folder"</string>
+ <string name="disabled_app_label" msgid="6673129024321402780">"Disabled <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <plurals name="dotted_app_label" formatted="false" msgid="5194538107138265416">
+ <item quantity="other"><xliff:g id="APP_NAME_2">%1$s</xliff:g>, has <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> notifications</item>
+ <item quantity="one"><xliff:g id="APP_NAME_0">%1$s</xliff:g>, has <xliff:g id="NOTIFICATION_COUNT_1">%2$d</xliff:g> notification</item>
+ </plurals>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d of %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Home screen %1$d of %2$d"</string>
+ <string name="workspace_new_page" msgid="257366611030256142">"New home screen page"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Folder opened, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="4625795376335528256">"Tap to close folder"</string>
+ <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tap to save rename"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Folder closed"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
+ <string name="styles_wallpaper_button_text" msgid="4342122323125579619">"Styles & wallpapers"</string>
+ <string name="settings_button_text" msgid="8873672322605444408">"Home settings"</string>
+ <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Disabled by your admin"</string>
+ <string name="allow_rotation_title" msgid="7728578836261442095">"Allow Home screen rotation"</string>
+ <string name="allow_rotation_desc" msgid="8662546029078692509">"When phone is rotated"</string>
+ <string name="notification_dots_title" msgid="9062440428204120317">"Notification dots"</string>
+ <string name="notification_dots_desc_on" msgid="1679848116452218908">"On"</string>
+ <string name="notification_dots_desc_off" msgid="1760796511504341095">"Off"</string>
+ <string name="title_missing_notification_access" msgid="7503287056163941064">"Notification access needed"</string>
+ <string name="msg_missing_notification_access" msgid="281113995110910548">"To show Notification Dots, turn on app notifications for <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="title_change_settings" msgid="1376365968844349552">"Change settings"</string>
+ <string name="notification_dots_service_title" msgid="4284221181793592871">"Show notification dots"</string>
+ <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Add icon to Home screen"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For new apps"</string>
+ <string name="package_state_unknown" msgid="7592128424511031410">"Unknown"</string>
+ <string name="abandoned_clean_this" msgid="7610119707847920412">"Remove"</string>
+ <string name="abandoned_search" msgid="891119232568284442">"Search"</string>
+ <string name="abandoned_promises_title" msgid="7096178467971716750">"This app is not installed"</string>
+ <string name="abandoned_promise_explanation" msgid="3990027586878167529">"The app for this icon isn\'t installed. You can remove it, or search for the app and install it manually."</string>
+ <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
+ <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
+ <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widgets"</string>
+ <string name="widgets_list" msgid="796804551140113767">"Widgets list"</string>
+ <string name="widgets_list_closed" msgid="6141506579418771922">"Widgets list closed"</string>
+ <string name="action_add_to_workspace" msgid="8902165848117513641">"Add to Home screen"</string>
+ <string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
+ <string name="item_added_to_workspace" msgid="4211073925752213539">"Item added to home screen"</string>
+ <string name="item_removed" msgid="851119963877842327">"Item removed"</string>
+ <string name="undo" msgid="4151576204245173321">"Undo"</string>
+ <string name="action_move" msgid="4339390619886385032">"Move item"</string>
+ <string name="move_to_empty_cell" msgid="2833711483015685619">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+ <string name="move_to_position" msgid="6750008980455459790">"Move to position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
+ <string name="move_to_hotseat_position" msgid="6295412897075147808">"Move to favorites position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
+ <string name="item_moved" msgid="4606538322571412879">"Item moved"</string>
+ <string name="add_to_folder" msgid="9040534766770853243">"Add to folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="add_to_folder_with_app" msgid="4534929978967147231">"Add to folder with <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="added_to_folder" msgid="4793259502305558003">"Item added to folder"</string>
+ <string name="create_folder_with" msgid="4050141361160214248">"Create folder with: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_created" msgid="6409794597405184510">"Folder created"</string>
+ <string name="action_move_to_workspace" msgid="1603837886334246317">"Move to Home screen"</string>
+ <string name="action_resize" msgid="1802976324781771067">"Resize"</string>
+ <string name="action_increase_width" msgid="8773715375078513326">"Increase width"</string>
+ <string name="action_increase_height" msgid="459390020612501122">"Increase height"</string>
+ <string name="action_decrease_width" msgid="1374549771083094654">"Decrease width"</string>
+ <string name="action_decrease_height" msgid="282377193880900022">"Decrease height"</string>
+ <string name="widget_resized" msgid="9130327887929620">"Widget resized to width <xliff:g id="NUMBER_0">%1$s</xliff:g> height <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+ <string name="action_deep_shortcut" msgid="2864038805849372848">"Shortcuts"</string>
+ <string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"Shortcuts and notifications"</string>
+ <string name="action_dismiss_notification" msgid="5909461085055959187">"Dismiss"</string>
+ <string name="notification_dismissed" msgid="6002233469409822874">"Notification dismissed"</string>
+ <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personal"</string>
+ <string name="all_apps_work_tab" msgid="4884822796154055118">"Work"</string>
+ <string name="work_profile_toggle_label" msgid="3081029915775481146">"Work profile"</string>
+ <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Find work apps here"</string>
+ <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Each work app has a badge and is kept secure by your organization. Move apps to your Home screen for easier access."</string>
+ <string name="work_mode_on_label" msgid="4781128097185272916">"Managed by your organization"</string>
+ <string name="work_mode_off_label" msgid="3194894777601421047">"Notifications and apps are off"</string>
+ <string name="bottom_work_tab_user_education_close_button" msgid="4224492243977802135">"Close"</string>
+ <string name="bottom_work_tab_user_education_closed" msgid="1098340939861869465">"Closed"</string>
+ <string name="remote_action_failed" msgid="1383965239183576790">"Failed: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index d10c84b..960ef93 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Trabajo"</string>
<string name="activity_not_found" msgid="8071924732094499514">"No se instaló la aplicación."</string>
<string name="activity_not_available" msgid="7456344436509528827">"La aplicación no está disponible."</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 09b1239..01f60e4 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Trabajo"</string>
<string name="activity_not_found" msgid="8071924732094499514">"La aplicación no está instalada."</string>
<string name="activity_not_available" msgid="7456344436509528827">"La aplicación no está disponible"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 1e470e1..ff121fa 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Töö"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Rakendus pole installitud."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Rakendus ei ole saadaval"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 9ba46c1..10aebe7 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Lana"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Aplikazioa instalatu gabe dago."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Ez dago erabilgarri aplikazioa"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 926cdb9..913ff48 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"کاری"</string>
<string name="activity_not_found" msgid="8071924732094499514">"برنامه نصب نشده است."</string>
<string name="activity_not_available" msgid="7456344436509528827">"برنامه در دسترس نیست"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index f87441f..e0930b6 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Työ"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Sovellusta ei ole asennettu."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Sovellus ei ole käytettävissä"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 5ac514d..50d6a06 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Lanceur3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Travail"</string>
<string name="activity_not_found" msgid="8071924732094499514">"L\'application n\'est pas installée."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Application indisponible"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 7b4bcb1..65db47e 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Android Work"</string>
<string name="activity_not_found" msgid="8071924732094499514">"L\'application n\'est pas installée."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Application indisponible"</string>
@@ -136,7 +135,7 @@
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Retrouvez ici vos applications professionnelles"</string>
<string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Les applications professionnelles sont accompagnées d\'un badge et sont sécurisées par votre organisation. Vous pouvez les déplacer vers votre écran d\'accueil pour y accéder plus facilement."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"Géré par votre organisation"</string>
- <string name="work_mode_off_label" msgid="3194894777601421047">"Les notifications et les applications sont désactivées"</string>
+ <string name="work_mode_off_label" msgid="3194894777601421047">"Notifications et applications désactivées"</string>
<string name="bottom_work_tab_user_education_close_button" msgid="4224492243977802135">"Fermer"</string>
<string name="bottom_work_tab_user_education_closed" msgid="1098340939861869465">"Fermé"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Échec : <xliff:g id="WHAT">%1$s</xliff:g>"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index e115a72..ca5ba3c 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Traballo"</string>
<string name="activity_not_found" msgid="8071924732094499514">"A aplicación non está instalada"</string>
<string name="activity_not_available" msgid="7456344436509528827">"A aplicación non está dispoñible"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 7def3ac..da9da38 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"કાર્યાલય"</string>
<string name="activity_not_found" msgid="8071924732094499514">"ઍપ્લિકેશન ઇન્સ્ટોલ થઈ નથી."</string>
<string name="activity_not_available" msgid="7456344436509528827">"ઍપ્લિકેશન ઉપલબ્ધ નથી"</string>
@@ -81,7 +80,7 @@
<string name="widget_button_text" msgid="2880537293434387943">"વિજેટ્સ"</string>
<string name="wallpaper_button_text" msgid="8404103075899945851">"વૉલપેપર્સ"</string>
<string name="styles_wallpaper_button_text" msgid="4342122323125579619">"શૈલીઓ અને વૉલપેપર"</string>
- <string name="settings_button_text" msgid="8873672322605444408">"હોમ સેટિંગ્સ"</string>
+ <string name="settings_button_text" msgid="8873672322605444408">"હોમ સેટિંગ"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"તમારા વ્યવસ્થાપક દ્વારા અક્ષમ કરેલ"</string>
<string name="allow_rotation_title" msgid="7728578836261442095">"હોમ સ્ક્રીનને ફેરવવાની મંજૂરી આપો"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"જ્યારે ફોન ફેરવવામાં આવે ત્યારે"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index b796140e..8fa02c0 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"कार्यस्थल"</string>
<string name="activity_not_found" msgid="8071924732094499514">"ऐप्लिकेशन इंस्टॉल नहीं है."</string>
<string name="activity_not_available" msgid="7456344436509528827">"ऐप्लिकेशन उपलब्ध नहीं है"</string>
@@ -88,7 +87,7 @@
<string name="notification_dots_title" msgid="9062440428204120317">"नई सूचनाएं बताने वाला गोल निशान"</string>
<string name="notification_dots_desc_on" msgid="1679848116452218908">"चालू"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"चालू"</string>
- <string name="title_missing_notification_access" msgid="7503287056163941064">"सूचना के एक्सेस की ज़रूरत है"</string>
+ <string name="title_missing_notification_access" msgid="7503287056163941064">"सूचना के ऐक्सेस की ज़रूरत है"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"सूचना बिंदु दिखाने के लिए, <xliff:g id="NAME">%1$s</xliff:g> के ऐप्लिकेशन सूचना चालू करें"</string>
<string name="title_change_settings" msgid="1376365968844349552">"सेटिंग बदलें"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"नई सूचनाएं बताने वाला गोल निशान दिखाएं"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 1caa27f..b738bc0 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Posao"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Aplikacija nije instalirana."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Aplikacija nije dostupna"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 3327590..7e26ca4 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Munka"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Az alkalmazás nincs telepítve."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Az alkalmazás nem érhető el"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index fbb657e..93404e6 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Աշխատանքային"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Ծրագիրը տեղադրված չէ:"</string>
<string name="activity_not_available" msgid="7456344436509528827">"Հավելվածը հասանելի չէ"</string>
@@ -87,7 +86,7 @@
<string name="allow_rotation_desc" msgid="8662546029078692509">"Հեռախոսը պտտելու դեպքում"</string>
<string name="notification_dots_title" msgid="9062440428204120317">"Ծանուցումների կետիկներ"</string>
<string name="notification_dots_desc_on" msgid="1679848116452218908">"Միացված է"</string>
- <string name="notification_dots_desc_off" msgid="1760796511504341095">"Անջատած է"</string>
+ <string name="notification_dots_desc_off" msgid="1760796511504341095">"Անջատված է"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"Անհրաժեշտ է ծանուցման թույլտվություն"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"Ծանուցումների կետիկները ցուցադրելու համար միացրեք ծանուցումները <xliff:g id="NAME">%1$s</xliff:g>-ի համար"</string>
<string name="title_change_settings" msgid="1376365968844349552">"Փոխել կարգավորումները"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 3054214..92c7acc 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Kantor"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Aplikasi tidak dipasang."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Aplikasi tidak tersedia"</string>
@@ -30,10 +29,10 @@
<string name="home_screen" msgid="806512411299847073">"Layar utama"</string>
<string name="custom_actions" msgid="3747508247759093328">"Tindakan khusus"</string>
<string name="long_press_widget_to_add" msgid="7699152356777458215">"Sentuh lama untuk memilih widget."</string>
- <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Tap dua kalip & tahan untuk mengambil widget atau menggunakan tindakan khusus."</string>
+ <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Ketuk dua kali & tahan untuk mengambil widget atau menggunakan tindakan khusus."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"lebar %1$d x tinggi %2$d"</string>
- <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Tap lama untuk menempatkan secara manual"</string>
+ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Sentuh lama untuk menempatkan secara manual"</string>
<string name="place_automatically" msgid="8064208734425456485">"Tambahkan otomatis"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Telusuri aplikasi"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"Memuat aplikasi…"</string>
@@ -41,8 +40,8 @@
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Telusuri aplikasi lainnya"</string>
<string name="label_application" msgid="8531721983832654978">"Aplikasi"</string>
<string name="notifications_header" msgid="1404149926117359025">"Notifikasi"</string>
- <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Tap lama untuk memilih pintasan."</string>
- <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Tap dua kali & tahan untuk memilih pintasan atau menggunakan tindakan khusus."</string>
+ <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Sentuh lama untuk memilih pintasan."</string>
+ <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Ketuk dua kali & tahan untuk memilih pintasan atau menggunakan tindakan khusus."</string>
<string name="out_of_space" msgid="4691004494942118364">"Tidak ada ruang lagi pada layar Utama ini."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Tidak ada ruang tersisa di baki Favorit"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Daftar aplikasi"</string>
@@ -73,8 +72,8 @@
<string name="workspace_scroll_format" msgid="8458889198184077399">"Layar utama %1$d dari %2$d"</string>
<string name="workspace_new_page" msgid="257366611030256142">"Halaman layar utama baru"</string>
<string name="folder_opened" msgid="94695026776264709">"Folder dibuka, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
- <string name="folder_tap_to_close" msgid="4625795376335528256">"Tap untuk menutup folder"</string>
- <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tap untuk menyimpan ganti nama"</string>
+ <string name="folder_tap_to_close" msgid="4625795376335528256">"Ketuk untuk menutup folder"</string>
+ <string name="folder_tap_to_rename" msgid="4017685068016979677">"Ketuk untuk menyimpan ganti nama"</string>
<string name="folder_closed" msgid="4100806530910930934">"Folder ditutup"</string>
<string name="folder_renamed" msgid="1794088362165669656">"Folder diganti namanya menjadi <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index d66ca32..9d26c8c 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Vinna"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Forritið er ekki uppsett."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Forritið er ekki í boði"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 8c4e3c5..aaceb53 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Lavoro"</string>
<string name="activity_not_found" msgid="8071924732094499514">"App non installata."</string>
<string name="activity_not_available" msgid="7456344436509528827">"App non disponibile"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index c25e879..3525699 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"עבודה"</string>
<string name="activity_not_found" msgid="8071924732094499514">"האפליקציה לא מותקנת."</string>
<string name="activity_not_available" msgid="7456344436509528827">"האפליקציה אינה זמינה"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 354f020..dad4879 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"仕事用"</string>
<string name="activity_not_found" msgid="8071924732094499514">"このアプリはインストールされていません。"</string>
<string name="activity_not_available" msgid="7456344436509528827">"このアプリは使用できません"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index e9afef9..b348afb 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"სამუშაო"</string>
<string name="activity_not_found" msgid="8071924732094499514">"აპი არ არის დაყენებული."</string>
<string name="activity_not_available" msgid="7456344436509528827">"აპი მიუწვდომელია"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 1cd9045..bd25ac9 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Жұмыс"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Қолданба орнатылмаған."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Қолданба қол жетімді емес"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index f4a328d..9738042 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"ការងារ"</string>
<string name="activity_not_found" msgid="8071924732094499514">"មិនបានដំឡើងកម្មវិធី។"</string>
<string name="activity_not_available" msgid="7456344436509528827">"មិនមានកម្មវិធី"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 14c8300..67ea6a8 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"ಕೆಲಸ"</string>
<string name="activity_not_found" msgid="8071924732094499514">"ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಸ್ಥಾಪಿಸಲಾಗಿಲ್ಲ"</string>
<string name="activity_not_available" msgid="7456344436509528827">"ಅಪ್ಲಿಕೇಶನ್ ಲಭ್ಯವಿಲ್ಲ"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 58b65da..e2767f9 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"업무"</string>
<string name="activity_not_found" msgid="8071924732094499514">"앱이 설치되지 않았습니다."</string>
<string name="activity_not_available" msgid="7456344436509528827">"앱을 사용할 수 없음"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 046a662..d52446c 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Жумуш"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Колдонмо орнотулган эмес."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Колдонмо жеткиликтүү эмес"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index afe7664..e764347 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"ວຽກ"</string>
<string name="activity_not_found" msgid="8071924732094499514">"ແອັບຯບໍ່ໄດ້ຖືກຕິດຕັ້ງ."</string>
<string name="activity_not_available" msgid="7456344436509528827">"ແອັບຯໃຊ້ບໍ່ໄດ້"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 6571582..7f93ac8 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Darbas"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Programa neįdiegta."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Programa nepasiekiama"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 3da0fbb..13d88fe 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Darbs"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Lietotne nav instalēta."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Lietotne nav pieejama."</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 9550a5f..458fb73 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Стартер3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Работа"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Апликацијата не е инсталирана."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Апликацијата не е достапна"</string>
@@ -81,9 +80,9 @@
<string name="widget_button_text" msgid="2880537293434387943">"Виџети"</string>
<string name="wallpaper_button_text" msgid="8404103075899945851">"Тапети"</string>
<string name="styles_wallpaper_button_text" msgid="4342122323125579619">"Стилови и тапети"</string>
- <string name="settings_button_text" msgid="8873672322605444408">"Поставки за Home"</string>
+ <string name="settings_button_text" msgid="8873672322605444408">"Поставки за почетен екран"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"Оневозможено од администраторот"</string>
- <string name="allow_rotation_title" msgid="7728578836261442095">"Дозволете ротација на Почетниот екран"</string>
+ <string name="allow_rotation_title" msgid="7728578836261442095">"Дозволете ротација на почетниот екран"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Кога телефонот се ротира"</string>
<string name="notification_dots_title" msgid="9062440428204120317">"Точки за известување"</string>
<string name="notification_dots_desc_on" msgid="1679848116452218908">"Вклучено"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 4362e7c..a118368 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"ലോഞ്ചർ3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"ഔദ്യോഗികം"</string>
<string name="activity_not_found" msgid="8071924732094499514">"അപ്ലിക്കേഷൻ ഇൻസ്റ്റാളുചെയ്തിട്ടില്ല."</string>
<string name="activity_not_available" msgid="7456344436509528827">"അപ്ലിക്കേഷൻ ലഭ്യമല്ല"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index ab02ac6..1b0e753 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Ажил"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Апп суугаагүй байна."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Апп-г ашиглах боломжгүй"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 19c0697..69eb5ee 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"कार्य"</string>
<string name="activity_not_found" msgid="8071924732094499514">"अॅप इंस्टॉल केलेला नाही."</string>
<string name="activity_not_available" msgid="7456344436509528827">"अॅप उपलब्ध नाही"</string>
@@ -35,19 +34,19 @@
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d रूंद बाय %2$d उंच"</string>
<string name="add_item_request_drag_hint" msgid="5899764264480397019">"स्वतः ठेवण्यासाठी स्पर्श करा आणि धरून ठेवा"</string>
<string name="place_automatically" msgid="8064208734425456485">"आपोआप जोडा"</string>
- <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"अॅप्स शोधा"</string>
- <string name="all_apps_loading_message" msgid="5813968043155271636">"अॅप्स लोड करत आहे…"</string>
- <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" शी जुळणारे कोणतेही अॅप्स आढळले नाहीत"</string>
- <string name="all_apps_search_market_message" msgid="1366263386197059176">"अधिक अॅप्स शोधा"</string>
+ <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"अॅप्स शोधा"</string>
+ <string name="all_apps_loading_message" msgid="5813968043155271636">"अॅप्स लोड करत आहे…"</string>
+ <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" शी जुळणारे कोणतेही अॅप्स आढळले नाहीत"</string>
+ <string name="all_apps_search_market_message" msgid="1366263386197059176">"अधिक अॅप्स शोधा"</string>
<string name="label_application" msgid="8531721983832654978">"ॲप"</string>
<string name="notifications_header" msgid="1404149926117359025">"सूचना"</string>
<string name="long_press_shortcut_to_add" msgid="4524750017792716791">"शॉर्टकट निवडण्यासाठी स्पर्श करा आणि धरून ठेवा."</string>
<string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"शॉर्टकट निवडण्यासाठी किंवा कस्टम क्रिया वापरण्यासाठी दोनदा टॅप करा आणि धरून ठेवा."</string>
<string name="out_of_space" msgid="4691004494942118364">"या मुख्य स्क्रीनवर आणखी जागा नाही."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"आवडीच्या ट्रे मध्ये आणखी जागा नाही"</string>
- <string name="all_apps_button_label" msgid="8130441508702294465">"अॅप्स सूची"</string>
- <string name="all_apps_button_personal_label" msgid="1315764287305224468">"वैयक्तिक अॅप्स सूची"</string>
- <string name="all_apps_button_work_label" msgid="7270707118948892488">"कामाच्या ठिकाणी वापरली जाणाऱ्या अॅप्सची सूची"</string>
+ <string name="all_apps_button_label" msgid="8130441508702294465">"अॅप्स सूची"</string>
+ <string name="all_apps_button_personal_label" msgid="1315764287305224468">"वैयक्तिक अॅप्स सूची"</string>
+ <string name="all_apps_button_work_label" msgid="7270707118948892488">"कामाच्या ठिकाणी वापरली जाणाऱ्या ॲप्सची सूची"</string>
<string name="all_apps_home_button_label" msgid="252062713717058851">"होम"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"काढा"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"अनइंस्टॉल करा"</string>
@@ -83,7 +82,7 @@
<string name="styles_wallpaper_button_text" msgid="4342122323125579619">"शैली आणि वॉलपेपर"</string>
<string name="settings_button_text" msgid="8873672322605444408">"होम सेटिंग्ज"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"आपल्या प्रशासकाने अक्षम केले"</string>
- <string name="allow_rotation_title" msgid="7728578836261442095">"मुख्यस्क्रीन फिरविण्यास अनुमती द्या"</string>
+ <string name="allow_rotation_title" msgid="7728578836261442095">"मुख्य स्क्रीन फिरविण्यास अनुमती द्या"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"फोन फिरविला जातो तेव्हा"</string>
<string name="notification_dots_title" msgid="9062440428204120317">"सूचना बिंदू"</string>
<string name="notification_dots_desc_on" msgid="1679848116452218908">"सुरू"</string>
@@ -93,7 +92,7 @@
<string name="title_change_settings" msgid="1376365968844349552">"सेटिंग्ज बदला"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"सूचना बिंदू दाखवा"</string>
<string name="auto_add_shortcuts_label" msgid="8222286205987725611">"होम स्क्रीनवर आयकन जोडा"</string>
- <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"नवीन अॅप्ससाठी"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"नवीन ॲप्ससाठी"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"अज्ञात"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"काढा"</string>
<string name="abandoned_search" msgid="891119232568284442">"शोधा"</string>
@@ -136,7 +135,7 @@
<string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"कामाची अॅप्स येथे मिळवा"</string>
<string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"प्रत्येक कार्य अॅपला एक बॅज असतो आणि तो तुमच्या संस्थेकडून सुरक्षित ठेवला जातो. अधिक सहज अॅक्सेससाठी अॅप्स तुमच्या होम स्क्रीनवर हलवा."</string>
<string name="work_mode_on_label" msgid="4781128097185272916">"तुमच्या संस्थेकडून व्यवस्थापित"</string>
- <string name="work_mode_off_label" msgid="3194894777601421047">"सूचना आणि अॅप्स बंद आहेत"</string>
+ <string name="work_mode_off_label" msgid="3194894777601421047">"सूचना आणि अॅप्स बंद आहेत"</string>
<string name="bottom_work_tab_user_education_close_button" msgid="4224492243977802135">"बंद करा"</string>
<string name="bottom_work_tab_user_education_closed" msgid="1098340939861869465">"बंद केले"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"हे करता आले नाही: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 7d05412..492e6b6 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Kerja"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Apl tidak dipasang."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Apl tidak tersedia"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index fd1f7d9..78856f6 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"ဖွင့်တင်စက်၃"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"အလုပ်"</string>
<string name="activity_not_found" msgid="8071924732094499514">"အက်ပ်မထည့်သွင်းထားပါ"</string>
<string name="activity_not_available" msgid="7456344436509528827">"အက်ပ်လက်လှမ်း မမှီပါ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 2257367..356ed03 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Jobb"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Appen er ikke installert."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Appen er ikke tilgjengelig"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 1e7aee9..58d61f9 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"कार्य"</string>
<string name="activity_not_found" msgid="8071924732094499514">"अनुप्रयोग स्थापित छैन।"</string>
<string name="activity_not_available" msgid="7456344436509528827">"अनुप्रयोग उपलब्ध छैन"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index ec30d8c..c2dfb71 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Werk"</string>
<string name="activity_not_found" msgid="8071924732094499514">"App is niet geïnstalleerd."</string>
<string name="activity_not_available" msgid="7456344436509528827">"App is niet beschikbaar"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 4ddc903..2479d1b 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"ଲଞ୍ଚର୍3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"କାମ"</string>
<string name="activity_not_found" msgid="8071924732094499514">"ଆପ୍ ଇନଷ୍ଟଲ୍ ହୋଇନାହିଁ"</string>
<string name="activity_not_available" msgid="7456344436509528827">"ଆପ୍ ଉପଲବ୍ଧ ନାହିଁ"</string>
@@ -85,8 +84,8 @@
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"ଆପଣଙ୍କ ଆଡମିନଙ୍କ ଦ୍ୱାରା ଅକ୍ଷମ କରାଯାଇଛି"</string>
<string name="allow_rotation_title" msgid="7728578836261442095">"ହୋମ୍ ସ୍କ୍ରୀନ୍ ବୁଲାଇବା ଅନୁମତି ଦିଅନ୍ତୁ"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"ଯେତେବେଳେ ଫୋନକୁ ବୁଲାଯାଇଥାଏ"</string>
- <string name="notification_dots_title" msgid="9062440428204120317">"ବିଜ୍ଞପ୍ତି ବିନ୍ଦୁଗୁଡ଼ିକ"</string>
- <string name="notification_dots_desc_on" msgid="1679848116452218908">"ଚାଲୁ କରନ୍ତୁ"</string>
+ <string name="notification_dots_title" msgid="9062440428204120317">"ବିଜ୍ଞପ୍ତି ଡଟ୍ସ"</string>
+ <string name="notification_dots_desc_on" msgid="1679848116452218908">"ଚାଲୁ"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"ବିଜ୍ଞପ୍ତି ଆକ୍ସେସ୍ ଆବଶ୍ୟକ ଅଟେ"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"ବିଜ୍ଞପ୍ତି ବିନ୍ଦୁ ଦେଖାଇବାକୁ, <xliff:g id="NAME">%1$s</xliff:g> ପାଇଁ ଆପ୍ ବିଜ୍ଞପ୍ତି ଅନ୍ କରନ୍ତୁ"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index d4cd5be..24686c4 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"ਦਫ਼ਤਰ"</string>
<string name="activity_not_found" msgid="8071924732094499514">"ਐਪ ਇੰਸਟੌਲ ਨਹੀਂ ਕੀਤਾ ਹੋਇਆ ਹੈ।"</string>
<string name="activity_not_available" msgid="7456344436509528827">"ਐਪ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 6885960..48c8baf 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Praca"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Aplikacja nie jest zainstalowana."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Aplikacja niedostępna"</string>
@@ -130,7 +129,7 @@
<string name="widget_resized" msgid="9130327887929620">"Szerokość i wysokość widżetu zmieniła się na <xliff:g id="NUMBER_0">%1$s</xliff:g> x <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
<string name="action_deep_shortcut" msgid="2864038805849372848">"Skróty"</string>
<string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"Skróty i powiadomienia"</string>
- <string name="action_dismiss_notification" msgid="5909461085055959187">"Odrzuć"</string>
+ <string name="action_dismiss_notification" msgid="5909461085055959187">"Zamknij"</string>
<string name="notification_dismissed" msgid="6002233469409822874">"Powiadomienie odrzucone"</string>
<string name="all_apps_personal_tab" msgid="4190252696685155002">"Osobiste"</string>
<string name="all_apps_work_tab" msgid="4884822796154055118">"Praca"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 071b71e..e0d2c74 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Trabalho"</string>
<string name="activity_not_found" msgid="8071924732094499514">"A aplicação não está instalada."</string>
<string name="activity_not_available" msgid="7456344436509528827">"A aplicação não está disponível"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index e88ff18..bb4834f 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Tela de início 3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Trabalho"</string>
<string name="activity_not_found" msgid="8071924732094499514">"O app não está instalado."</string>
<string name="activity_not_available" msgid="7456344436509528827">"O app não está disponível"</string>
diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml
deleted file mode 100644
index 0758148..0000000
--- a/res/values-rm/strings.xml
+++ /dev/null
@@ -1,203 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-/*
-* Copyright (C) 2008 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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for application_name (5181331383435256801) -->
- <skip />
- <!-- no translation found for home (7658288663002113681) -->
- <skip />
- <!-- no translation found for uid_name (7820867637514617527) -->
- <skip />
- <string name="folder_name" msgid="7371454440695724752"></string>
- <!-- no translation found for activity_not_found (8071924732094499514) -->
- <skip />
- <!-- no translation found for widgets_tab_label (2921133187116603919) -->
- <skip />
- <!-- no translation found for widget_adder (3201040140710381657) -->
- <skip />
- <!-- no translation found for toggle_weight_watcher (5645299835184636119) -->
- <skip />
- <!-- no translation found for long_press_widget_to_add (7699152356777458215) -->
- <skip />
- <!-- no translation found for market (2619650989819296998) -->
- <skip />
- <!-- no translation found for widget_dims_format (2370757736025621599) -->
- <skip />
- <!-- no translation found for external_drop_widget_error (3165821058322217155) -->
- <skip />
- <!-- no translation found for external_drop_widget_pick_title (3486317258037690630) -->
- <skip />
- <!-- no translation found for rename_folder_label (3727762225964550653) -->
- <skip />
- <!-- no translation found for rename_folder_title (3771389277707820891) -->
- <skip />
- <!-- no translation found for rename_action (5559600076028658757) -->
- <skip />
- <!-- no translation found for cancel_action (7009134900002915310) -->
- <skip />
- <!-- no translation found for menu_item_add_item (1264911265836810421) -->
- <skip />
- <!-- no translation found for group_applications (3797214114206693605) -->
- <skip />
- <!-- no translation found for group_shortcuts (6012256992764410535) -->
- <skip />
- <!-- no translation found for group_widgets (1569030723286851002) -->
- <skip />
- <!-- no translation found for completely_out_of_space (6106288382070760318) -->
- <skip />
- <!-- no translation found for out_of_space (4691004494942118364) -->
- <skip />
- <!-- no translation found for hotseat_out_of_space (7448809638125333693) -->
- <skip />
- <!-- no translation found for invalid_hotseat_item (5779907847267573691) -->
- <skip />
- <!-- no translation found for shortcut_installed (1701742129426969556) -->
- <skip />
- <!-- no translation found for shortcut_uninstalled (8176767991305701821) -->
- <skip />
- <!-- no translation found for shortcut_duplicate (9167217446062498127) -->
- <skip />
- <!-- no translation found for title_select_shortcut (6680642571148153868) -->
- <skip />
- <!-- no translation found for title_select_application (3280812711670683644) -->
- <skip />
- <!-- no translation found for all_apps_button_label (9110807029020582876) -->
- <skip />
- <!-- no translation found for all_apps_home_button_label (252062713717058851) -->
- <skip />
- <!-- no translation found for delete_zone_label_workspace (4009607676751398685) -->
- <skip />
- <!-- no translation found for delete_zone_label_all_apps (8083826390278958980) -->
- <skip />
- <!-- no translation found for delete_target_label (1822697352535677073) -->
- <skip />
- <!-- no translation found for delete_target_uninstall_label (5100785476250872595) -->
- <skip />
- <!-- no translation found for info_target_label (8053346143994679532) -->
- <skip />
- <!-- no translation found for accessibility_search_button (1628520399424565142) -->
- <skip />
- <!-- no translation found for accessibility_voice_search_button (4637324840434406584) -->
- <skip />
- <!-- no translation found for accessibility_all_apps_button (2603132375383800483) -->
- <skip />
- <!-- no translation found for accessibility_delete_button (6466114477993744621) -->
- <skip />
- <!-- no translation found for delete_zone_label_all_apps_system_app (449755632749610895) -->
- <skip />
- <!-- no translation found for cab_menu_delete_app (7435191475867183689) -->
- <skip />
- <!-- no translation found for cab_menu_app_info (8593722221450362342) -->
- <skip />
- <!-- no translation found for cab_app_selection_text (374688303047985416) -->
- <skip />
- <!-- no translation found for cab_widget_selection_text (1833458597831541241) -->
- <skip />
- <!-- no translation found for cab_folder_selection_text (7999992513806132118) -->
- <skip />
- <!-- no translation found for cab_shortcut_selection_text (2103811025667946450) -->
- <skip />
- <!-- no translation found for permlab_install_shortcut (5632423390354674437) -->
- <skip />
- <!-- no translation found for permdesc_install_shortcut (923466509822011139) -->
- <skip />
- <!-- no translation found for permlab_uninstall_shortcut (864595034498083837) -->
- <skip />
- <!-- no translation found for permdesc_uninstall_shortcut (5134129545001836849) -->
- <skip />
- <!-- no translation found for permlab_read_settings (1941457408239617576) -->
- <skip />
- <!-- no translation found for permdesc_read_settings (5833423719057558387) -->
- <skip />
- <!-- no translation found for permlab_write_settings (3574213698004620587) -->
- <skip />
- <!-- no translation found for permdesc_write_settings (5440712911516509985) -->
- <skip />
- <!-- no translation found for gadget_error_text (6081085226050792095) -->
- <skip />
- <!-- no translation found for uninstall_system_app_text (4172046090762920660) -->
- <skip />
- <!-- no translation found for dream_name (1530253749244328964) -->
- <skip />
- <!-- no translation found for folder_hint_text (6617836969016293992) -->
- <skip />
- <!-- no translation found for workspace_description_format (2950174241104043327) -->
- <skip />
- <!-- no translation found for default_scroll_format (7475544710230993317) -->
- <skip />
- <!-- no translation found for workspace_scroll_format (8458889198184077399) -->
- <skip />
- <!-- no translation found for apps_customize_apps_scroll_format (370005296147130238) -->
- <skip />
- <!-- no translation found for apps_customize_widgets_scroll_format (3106209519974971521) -->
- <skip />
- <!-- no translation found for first_run_cling_title (2459738000155917941) -->
- <skip />
- <!-- no translation found for first_run_cling_description (6447072552696253358) -->
- <skip />
- <!-- no translation found for first_run_cling_create_screens_hint (6950729526680114157) -->
- <skip />
- <!-- no translation found for migration_cling_title (9181776667882933767) -->
- <skip />
- <!-- no translation found for migration_cling_description (2752413805582227644) -->
- <skip />
- <!-- no translation found for migration_cling_copy_apps (946331230090919440) -->
- <skip />
- <!-- no translation found for migration_cling_use_default (2626475813981258626) -->
- <skip />
- <!-- no translation found for workspace_cling_title (5626202359865825661) -->
- <skip />
- <!-- no translation found for workspace_cling_move_item (528201129978005352) -->
- <skip />
- <!-- no translation found for folder_cling_title (3894908818693254164) -->
- <skip />
- <!-- no translation found for folder_cling_create_folder (6158215559475836131) -->
- <skip />
- <!-- no translation found for cling_dismiss (8962359497601507581) -->
- <skip />
- <!-- no translation found for folder_opened (94695026776264709) -->
- <skip />
- <!-- no translation found for folder_tap_to_close (1884479294466410023) -->
- <skip />
- <!-- no translation found for folder_tap_to_rename (9191075570492871147) -->
- <skip />
- <!-- no translation found for folder_closed (4100806530910930934) -->
- <skip />
- <!-- no translation found for folder_renamed (1794088362165669656) -->
- <skip />
- <!-- no translation found for folder_name_format (6629239338071103179) -->
- <skip />
- <!-- no translation found for widget_button_text (2880537293434387943) -->
- <skip />
- <!-- no translation found for wallpaper_button_text (8404103075899945851) -->
- <skip />
- <!-- no translation found for settings_button_text (8119458837558863227) -->
- <skip />
- <!-- no translation found for package_state_enqueued (6227252464303085641) -->
- <skip />
- <!-- no translation found for package_state_downloading (4088770468458724721) -->
- <skip />
- <!-- no translation found for package_state_installing (7588193972189849870) -->
- <skip />
- <!-- no translation found for package_state_unknown (7592128424511031410) -->
- <skip />
- <!-- no translation found for package_state_error (7672093962724223588) -->
- <skip />
-</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 1251d7e..f8a61f1 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Aplicația nu este instalată."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Aplicația nu este disponibilă"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 34e267d..1cc699f 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Работа"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Приложение удалено"</string>
<string name="activity_not_available" msgid="7456344436509528827">"Приложение недоступно"</string>
@@ -90,7 +89,7 @@
<string name="notification_dots_title" msgid="9062440428204120317">"Значки уведомлений"</string>
<string name="notification_dots_desc_on" msgid="1679848116452218908">"Включены"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"Отключены"</string>
- <string name="title_missing_notification_access" msgid="7503287056163941064">"Нет доступа к уведомлениям"</string>
+ <string name="title_missing_notification_access" msgid="7503287056163941064">"Нужен доступ к уведомлениям"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"Чтобы показывать значки уведомлений, включите уведомления в приложении \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
<string name="title_change_settings" msgid="1376365968844349552">"Изменить настройки"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"Показывать значки уведомлений"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index ef99a59..2f9dc42 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"කාර්යාලය"</string>
<string name="activity_not_found" msgid="8071924732094499514">"යෙදුම ස්ථාපනය කර නැත."</string>
<string name="activity_not_available" msgid="7456344436509528827">"යෙදුම නොතිබේ"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 340a128..5bbf7c3 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Pracovné"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Aplikácia nie je nainštalovaná."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Aplikácia nie je k dispozícii"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 044a4b4..417151b 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Služba"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Aplikacija ni nameščena."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Aplikacija ni na voljo"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 48ae26e..7f2567c 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Nisësi3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Puna"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Aplikacioni nuk është i instaluar."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Aplikacioni nuk mundësohet"</string>
@@ -41,8 +40,8 @@
<string name="all_apps_search_market_message" msgid="1366263386197059176">"Kërko për më shumë aplikacione"</string>
<string name="label_application" msgid="8531721983832654978">"Aplikacioni"</string>
<string name="notifications_header" msgid="1404149926117359025">"Njoftimet"</string>
- <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Prek dhe mbaj prekur për të zgjedhur një shkurtore."</string>
- <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Prek dy herë dhe mbaj prekur për të zgjedhur një shkurtore ose për të përdorur veprimet e personalizuara."</string>
+ <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Prek dhe mbaj të shtypur për të zgjedhur një shkurtore."</string>
+ <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Prek dy herë dhe mbaj të shtypur për të zgjedhur një shkurtore ose për të përdorur veprimet e personalizuara."</string>
<string name="out_of_space" msgid="4691004494942118364">"Nuk ka më hapësirë në këtë ekran bazë."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Nuk ka më hapësirë në tabakanë \"Të preferuarat\""</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"Lista e aplikacioneve"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 4e52592..5e2c954 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Апликација није инсталирана."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Апликација није доступна"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index e7400a6..7df507b 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Arbete"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Appen är inte installerad."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Appen är inte tillgänglig"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 6f33c4f..445e382 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Kazini"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Programu haijasakinishwa."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Programu haipatikani"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 672665e..799b6b8 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"பணியிடம்"</string>
<string name="activity_not_found" msgid="8071924732094499514">"ஆப்ஸ் நிறுவப்படவில்லை."</string>
<string name="activity_not_available" msgid="7456344436509528827">"ஆப்ஸ் இல்லை"</string>
@@ -36,8 +35,8 @@
<string name="add_item_request_drag_hint" msgid="5899764264480397019">"நீங்களே சேர்க்க, தொட்டுப் பிடித்திருக்கவும்"</string>
<string name="place_automatically" msgid="8064208734425456485">"தானாகவே சேர்"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"பயன்பாடுகளில் தேடுக"</string>
- <string name="all_apps_loading_message" msgid="5813968043155271636">"பயன்பாடுகளை ஏற்றுகிறது…"</string>
- <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" உடன் பொருந்தும் பயன்பாடுகள் இல்லை"</string>
+ <string name="all_apps_loading_message" msgid="5813968043155271636">"ஆப்ஸை ஏற்றுகிறது…"</string>
+ <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" உடன் பொருந்தும் ஆப்ஸ் இல்லை"</string>
<string name="all_apps_search_market_message" msgid="1366263386197059176">"கூடுதல் பயன்பாடுகளைத் தேடு"</string>
<string name="label_application" msgid="8531721983832654978">"ஆப்ஸ்"</string>
<string name="notifications_header" msgid="1404149926117359025">"அறிவிப்புகள்"</string>
@@ -45,7 +44,7 @@
<string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"ஷார்ட்கட்டைச் சேர்க்க, இருமுறை தட்டிப் பிடித்திருக்கவும் (அ) தனிப்பயன் செயல்களைப் பயன்படுத்தவும்."</string>
<string name="out_of_space" msgid="4691004494942118364">"முகப்புத் திரையில் இடமில்லை."</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"பிடித்தவை ட்ரேயில் இடமில்லை"</string>
- <string name="all_apps_button_label" msgid="8130441508702294465">"பயன்பாடுகளின் பட்டியல்"</string>
+ <string name="all_apps_button_label" msgid="8130441508702294465">"ஆப்ஸின் பட்டியல்"</string>
<string name="all_apps_button_personal_label" msgid="1315764287305224468">"தனிப்பட்ட ஆப்ஸ் பட்டியல்"</string>
<string name="all_apps_button_work_label" msgid="7270707118948892488">"பணி ஆப்ஸ் பட்டியல்"</string>
<string name="all_apps_home_button_label" msgid="252062713717058851">"முகப்பு"</string>
@@ -89,7 +88,7 @@
<string name="notification_dots_desc_on" msgid="1679848116452218908">"ஆன்"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"ஆஃப்"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"அறிவிப்பிற்கான அணுகல் தேவை"</string>
- <string name="msg_missing_notification_access" msgid="281113995110910548">"அறிவிப்புப் புள்ளிகளைக் காட்ட, <xliff:g id="NAME">%1$s</xliff:g> இன் பயன்பாட்டு அறிவிப்புகளை இயக்கவும்"</string>
+ <string name="msg_missing_notification_access" msgid="281113995110910548">"அறிவிப்புப் புள்ளிகளைக் காட்ட, <xliff:g id="NAME">%1$s</xliff:g> இன் ஆப்ஸ் அறிவிப்புகளை இயக்கவும்"</string>
<string name="title_change_settings" msgid="1376365968844349552">"அமைப்புகளை மாற்று"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"அறிவிப்புப் புள்ளிகளைக் காட்டு"</string>
<string name="auto_add_shortcuts_label" msgid="8222286205987725611">"முகப்புத் திரையில் ஐகானைச் சேர்"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index c8f70fe..1f8d3b8 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"కార్యాలయం"</string>
<string name="activity_not_found" msgid="8071924732094499514">"యాప్ ఇన్స్టాల్ చేయబడలేదు."</string>
<string name="activity_not_available" msgid="7456344436509528827">"యాప్ అందుబాటులో లేదు"</string>
@@ -53,7 +52,7 @@
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"అన్ఇన్స్టాల్ చేయి"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"యాప్ సమాచారం"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ఇన్స్టాల్ చేయండి"</string>
- <string name="permlab_install_shortcut" msgid="5632423390354674437">"సత్వరమార్గాలను ఇన్స్టాల్ చేయడం"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"షార్ట్కట్లను ఇన్స్టాల్ చేయడం"</string>
<string name="permdesc_install_shortcut" msgid="923466509822011139">"వినియోగదారు ప్రమేయం లేకుండా సత్వరమార్గాలను జోడించడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string>
<string name="permlab_read_settings" msgid="1941457408239617576">"హోమ్ సెట్టింగ్లు మరియు సత్వరమార్గాలను చదవడం"</string>
<string name="permdesc_read_settings" msgid="5833423719057558387">"హోమ్లో సెట్టింగ్లు మరియు సత్వరమార్గాలను చదవడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 03c02ad..5ab9b86 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"งาน"</string>
<string name="activity_not_found" msgid="8071924732094499514">"ไม่ได้ติดตั้งแอป"</string>
<string name="activity_not_available" msgid="7456344436509528827">"แอปไม่พร้อมใช้งาน"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 0df94c7..de4863a 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Trabaho"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Hindi naka-install ang app."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Hindi available ang app"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 56f8447..1b2b3d1 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"İş"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Uygulama yüklü değil."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Uygulama kullanılamıyor"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 13ba701..622cdcc 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Робоча папка"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Додаток видалено."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Додаток недоступний"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 4f77670..74b5eaa 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"دفتری"</string>
<string name="activity_not_found" msgid="8071924732094499514">"ایپ انسٹال نہیں ہے۔"</string>
<string name="activity_not_available" msgid="7456344436509528827">"ایپ دستیاب نہیں ہے"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 69084d7..dac1ac9 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Ishga oid"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Ilova o‘rnatilmadi."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Ilova mavjud emas"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 71decfc..4dc58b9 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Trình chạy 3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Ứng dụng chưa được cài đặt."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Ứng dụng không có sẵn"</string>
@@ -86,7 +85,7 @@
<string name="allow_rotation_title" msgid="7728578836261442095">"Cho phép xoay Màn hình chính"</string>
<string name="allow_rotation_desc" msgid="8662546029078692509">"Khi xoay điện thoại"</string>
<string name="notification_dots_title" msgid="9062440428204120317">"Dấu chấm thông báo"</string>
- <string name="notification_dots_desc_on" msgid="1679848116452218908">"Bật"</string>
+ <string name="notification_dots_desc_on" msgid="1679848116452218908">"Đang bật"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"Tắt"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"Cần quyền truy cập thông báo"</string>
<string name="msg_missing_notification_access" msgid="281113995110910548">"Để hiển thị Dấu chấm thông báo, hãy bật thông báo ứng dụng cho <xliff:g id="NAME">%1$s</xliff:g>"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 9804af1..0478627 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
<string name="activity_not_found" msgid="8071924732094499514">"未安装该应用。"</string>
<string name="activity_not_available" msgid="7456344436509528827">"应用不可用"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index e737744..ed53c32 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"工作"</string>
<string name="activity_not_found" msgid="8071924732094499514">"尚未安裝應用程式。"</string>
<string name="activity_not_available" msgid="7456344436509528827">"目前無法使用這個應用程式"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index e971b69..14f2e06 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Launcher3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"公司"</string>
<string name="activity_not_found" msgid="8071924732094499514">"應用程式未安裝。"</string>
<string name="activity_not_available" msgid="7456344436509528827">"應用程式目前無法使用"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 83178fe..b937764 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -20,7 +20,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Isiqalisi3"</string>
- <string name="folder_name" msgid="7371454440695724752"></string>
<string name="work_folder_name" msgid="3753320833950115786">"Umsebenzi"</string>
<string name="activity_not_found" msgid="8071924732094499514">"Uhlelo lokusebenza alufakiwe."</string>
<string name="activity_not_available" msgid="7456344436509528827">"Uhlelo lokusebenza alutholakali"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 69b8c8a..de17eb7 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -115,7 +115,6 @@
<attr name="numFolderColumns" format="integer" />
<!-- numHotseatIcons defaults to numColumns, if not specified -->
<attr name="numHotseatIcons" format="integer" />
-
<attr name="defaultLayoutId" format="reference" />
<attr name="demoModeLayoutId" format="reference" />
</declare-styleable>
@@ -152,21 +151,6 @@
<attr name="canThumbDetach" format="boolean" />
</declare-styleable>
- <declare-styleable name="CustomAppWidgetProviderInfo">
- <attr name="providerId" format="integer" />
-
- <attr name="android:label" />
- <attr name="android:initialLayout" />
- <attr name="android:icon" />
- <attr name="android:previewImage" />
- <attr name="android:resizeMode" />
-
- <attr name="numRows" />
- <attr name="numColumns" />
- <attr name="numMinRows" format="integer" />
- <attr name="numMinColumns" format="integer" />
- </declare-styleable>
-
<declare-styleable name="PreviewFragment">
<attr name="android:name" />
<attr name="android:id" />
diff --git a/res/values/config.xml b/res/values/config.xml
index 638a411..9d36ee4 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -72,6 +72,7 @@
<string name="system_shortcut_factory_class" translatable="false"></string>
<string name="app_launch_tracker_class" translatable="false"></string>
<string name="test_information_handler_class" translatable="false"></string>
+ <string name="launcher_activity_logic_class" translatable="false"></string>
<!-- Package name of the default wallpaper picker. -->
<string name="wallpaper_picker_package" translatable="false"></string>
@@ -117,4 +118,7 @@
<!-- Recents -->
<item type="id" name="overview_panel"/>
+
+ <string-array name="live_wallpapers_remove_sysui_scrims">
+ </string-array>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 13e096c..9d9c2e8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -23,8 +23,6 @@
<!-- Application name -->
<string name="app_name">Launcher3</string>
- <!-- Default folder name -->
- <string name="folder_name"></string>
<!-- Work folder name -->
<string name="work_folder_name">Work</string>
<!-- Displayed when user selects a shortcut for an app that was uninstalled [CHAR_LIMIT=none]-->
@@ -105,6 +103,9 @@
<!-- Label for install drop target. [CHAR_LIMIT=20] -->
<string name="install_drop_target_label">Install</string>
+ <!-- Label for install dismiss prediction. -->
+ <string translatable="false" name="dismiss_prediction_label">Dismiss prediction</string>
+
<!-- Permissions: -->
<skip />
<!-- Permission short label -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 881f65d..80c791c 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -104,6 +104,7 @@
</style>
<style name="LauncherTheme.Dark.DarkText" parent="@style/LauncherTheme.Dark">
+ <item name="android:colorControlHighlight">#75212121</item>
<item name="allAppsInterimScrimAlpha">25</item>
<item name="folderFillColor">#CDFFFFFF</item>
<item name="folderTextColor">?attr/workspaceTextColor</item>
@@ -129,6 +130,10 @@
<item name="widgetsTheme">@style/WidgetContainerTheme</item>
</style>
+ <style name="HomeSettingsTheme" parent="@android:style/Theme.DeviceDefault.Settings">
+ <item name="android:navigationBarColor">@android:color/transparent</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/custom_widgets.xml b/res/xml/custom_widgets.xml
deleted file mode 100644
index 4b54386..0000000
--- a/res/xml/custom_widgets.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-
-<widgets>
- <!-- Sample widget definition
- <widget
- android:label="My custom widget"
- android:initialLayout="@layout/sample_widget_layout"
- android:icon="@drawable/ic_launcher_home"
- android:resizeMode="horizontal|vertical"
- launcher:numRows="2"
- launcher:numColumns="3"
- launcher:numMinRows="1"
- launcher:numMinColumns="2"
- launcher:providerId="1" />
- -->
-</widgets>
\ No newline at end of file
diff --git a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java
index 92bcc64..a3d1216 100644
--- a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java
+++ b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java
@@ -1,6 +1,8 @@
package com.android.launcher3.config;
+import com.android.launcher3.config.BaseFlags.BaseTogglableFlag;
+import com.android.launcher3.uioverrides.TogglableFlag;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
@@ -70,7 +72,7 @@
};
}
- private void override(BaseFlags.TogglableFlag flag, boolean newValue) {
+ private void override(BaseTogglableFlag flag, boolean newValue) {
if (!ruleInProgress) {
throw new IllegalStateException(
"Rule isn't in progress. Did you remember to mark it with @Rule?");
@@ -93,7 +95,7 @@
private void applyAnnotation(FlagOverride flagOverride) {
boolean found = false;
- for (BaseFlags.TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
+ for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
if (flag.getKey().equals(flagOverride.key())) {
override(flag, flagOverride.value());
found = true;
@@ -109,7 +111,7 @@
* Resets all flags to their default values.
*/
private void clearOverrides() {
- for (BaseFlags.TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
+ for (BaseTogglableFlag flag : FeatureFlags.getTogglableFlags()) {
flag.setForTests(flag.getDefaultValue());
}
}
diff --git a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
index ab39274..bc936b7 100644
--- a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -15,14 +15,13 @@
import android.os.Process;
import android.os.UserHandle;
-import com.android.launcher3.AllAppsList;
import com.android.launcher3.AppFilter;
import com.android.launcher3.AppInfo;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.icons.IconCache;
diff --git a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
index e9324f9..42a4f5c 100644
--- a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -29,7 +29,8 @@
private PackageInstallStateChangedTask newTask(String pkg, int progress) {
int state = PackageInstallerCompat.STATUS_INSTALLING;
- PackageInstallInfo installInfo = new PackageInstallInfo(pkg, state, progress);
+ PackageInstallInfo installInfo = new PackageInstallInfo(pkg, state, progress,
+ android.os.Process.myUserHandle());
return new PackageInstallStateChangedTask(installInfo);
}
diff --git a/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java b/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java
new file mode 100644
index 0000000..c08e198
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 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 static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * Robolectric unit tests for {@link IntArray}
+ */
+@RunWith(RobolectricTestRunner.class)
+public class IntArrayTest {
+
+ @Test
+ public void concatAndParseString() {
+ int[] array = new int[] {0, 2, 3, 9};
+ String concat = IntArray.wrap(array).toConcatString();
+
+ int[] parsed = IntArray.fromConcatString(concat).toArray();
+ assertThat(array).isEqualTo(parsed);
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
index f846de5..8513353 100644
--- a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
+++ b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
@@ -21,7 +21,6 @@
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 65f9d6b..af2cdc3 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -16,7 +16,6 @@
package com.android.launcher3;
-import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
@@ -30,6 +29,7 @@
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.LinearLayout;
import androidx.annotation.IntDef;
@@ -86,7 +86,7 @@
// Type of popups which should be kept open during launcher rebind
public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
- | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE;
+ | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE;
// Usually we show the back button when a floating view is open. Instead, hide for these types.
public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
@@ -171,7 +171,7 @@
targetInfo.first, TYPE_WINDOW_STATE_CHANGED, targetInfo.second);
if (mIsOpen) {
- sendAccessibilityEvent(TYPE_VIEW_FOCUSED);
+ performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
}
ActivityContext.lookupContext(getContext()).getDragLayer()
.sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index d884049..c8e7619 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -29,11 +29,19 @@
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageManagerHelper;
+import java.util.Comparator;
+
/**
* Represents an app in AllAppsView.
*/
public class AppInfo extends ItemInfoWithIcon {
+ public static AppInfo[] EMPTY_ARRAY = new AppInfo[0];
+ public static Comparator<AppInfo> COMPONENT_KEY_COMPARATOR = (a, b) -> {
+ int uc = a.user.hashCode() - b.user.hashCode();
+ return uc != 0 ? uc : a.componentName.compareTo(b.componentName);
+ };
+
/**
* The intent used to start the application.
*/
@@ -41,6 +49,9 @@
public ComponentName componentName;
+ // Section name used for indexing.
+ public String sectionName = "";
+
public AppInfo() {
itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
}
@@ -74,6 +85,8 @@
componentName = info.componentName;
title = Utilities.trim(info.title);
intent = new Intent(info.intent);
+ user = info.user;
+ runtimeStatusFlags = info.runtimeStatusFlags;
}
@Override
@@ -116,4 +129,9 @@
info.runtimeStatusFlags |= FLAG_ADAPTIVE_ICON;
}
}
+
+ @Override
+ public AppInfo clone() {
+ return new AppInfo(this);
+ }
}
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index d949141..e3ef5d6 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -11,6 +11,7 @@
import android.util.Log;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.provider.RestoreDbTask;
@@ -18,6 +19,8 @@
import androidx.annotation.WorkerThread;
+import static android.os.Process.myUserHandle;
+
public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
private static final String TAG = "AWRestoredReceiver";
@@ -77,9 +80,14 @@
state = LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
}
- String[] widgetIdParams = new String[] { Integer.toString(oldWidgetIds[i]) };
+ // b/135926478: Work profile widget restore is broken in platform. This forces us to
+ // recreate the widget during loading with the correct host provider.
+ long mainProfileId = UserManagerCompat.getInstance(context)
+ .getSerialNumberForUser(myUserHandle());
+ String oldWidgetId = Integer.toString(oldWidgetIds[i]);
int result = new ContentWriter(context, new ContentWriter.CommitParams(
- "appWidgetId=? and (restored & 1) = 1", widgetIdParams))
+ "appWidgetId=? and (restored & 1) = 1 and profileId=?",
+ new String[] { oldWidgetId, Long.toString(mainProfileId) }))
.put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i])
.put(LauncherSettings.Favorites.RESTORED, state)
.commit();
@@ -87,7 +95,7 @@
if (result == 0) {
Cursor cursor = cr.query(Favorites.CONTENT_URI,
new String[] {Favorites.APPWIDGET_ID},
- "appWidgetId=?", widgetIdParams, null);
+ "appWidgetId=?", new String[] { oldWidgetId }, null);
try {
if (!cursor.moveToFirst()) {
// The widget no long exists.
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 9724869..ac43967 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -38,11 +38,15 @@
import android.util.Patterns;
import android.util.Xml;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.LauncherProvider.SqlArguments;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.qsb.QsbContainerView;
import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Thunk;
import org.xmlpull.v1.XmlPullParser;
@@ -72,7 +76,7 @@
static AutoInstallsLayout get(Context context, AppWidgetHost appWidgetHost,
LayoutParserCallback callback) {
- Pair<String, Resources> customizationApkInfo = Utilities.findSystemApk(
+ Pair<String, Resources> customizationApkInfo = PackageManagerHelper.findSystemApk(
ACTION_LAUNCHER_CUSTOMIZATION, context.getPackageManager());
if (customizationApkInfo == null) {
return null;
@@ -83,7 +87,7 @@
// Try with grid size and hotseat count
String layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES_WITH_HOSTEAT,
- grid.numColumns, grid.numRows, grid.numHotseatIcons);
+ grid.numColumns, grid.numRows, grid.numHotseatIcons);
int layoutId = targetRes.getIdentifier(layoutName, "xml", pkg);
// Try with only grid size
@@ -91,7 +95,7 @@
Log.d(TAG, "Formatted layout: " + layoutName
+ " not found. Trying layout without hosteat");
layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES,
- grid.numColumns, grid.numRows);
+ grid.numColumns, grid.numRows);
layoutId = targetRes.getIdentifier(layoutName, "xml", pkg);
}
@@ -116,6 +120,7 @@
private static final String TAG_AUTO_INSTALL = "autoinstall";
private static final String TAG_FOLDER = "folder";
private static final String TAG_APPWIDGET = "appwidget";
+ protected static final String TAG_SEARCH_WIDGET = "searchwidget";
private static final String TAG_SHORTCUT = "shortcut";
private static final String TAG_EXTRA = "extra";
@@ -147,8 +152,10 @@
private static final String HOTSEAT_CONTAINER_NAME =
Favorites.containerToString(Favorites.CONTAINER_HOTSEAT);
- @Thunk final Context mContext;
- @Thunk final AppWidgetHost mAppWidgetHost;
+ @Thunk
+ final Context mContext;
+ @Thunk
+ final AppWidgetHost mAppWidgetHost;
protected final LayoutParserCallback mCallback;
protected final PackageManager mPackageManager;
@@ -160,7 +167,8 @@
private final int mColumnCount;
private final int[] mTemp = new int[2];
- @Thunk final ContentValues mValues;
+ @Thunk
+ final ContentValues mValues;
protected final String mRootTag;
protected SQLiteDatabase mDb;
@@ -244,7 +252,7 @@
*/
protected int parseAndAddNode(
XmlPullParser parser, ArrayMap<String, TagParser> tagParserMap, IntArray screenIds)
- throws XmlPullParserException, IOException {
+ throws XmlPullParserException, IOException {
if (TAG_INCLUDE.equals(parser.getName())) {
final int resId = getAttributeResourceValue(parser, ATTR_WORKSPACE, 0);
@@ -315,6 +323,7 @@
parsers.put(TAG_AUTO_INSTALL, new AutoInstallParser());
parsers.put(TAG_FOLDER, new FolderParser());
parsers.put(TAG_APPWIDGET, new PendingWidgetParser());
+ parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser());
parsers.put(TAG_SHORTCUT, new ShortcutParser(mSourceRes));
return parsers;
}
@@ -347,15 +356,15 @@
info = mPackageManager.getActivityInfo(cn, 0);
} catch (PackageManager.NameNotFoundException nnfe) {
String[] packages = mPackageManager.currentToCanonicalPackageNames(
- new String[] { packageName });
+ new String[]{packageName});
cn = new ComponentName(packages[0], className);
info = mPackageManager.getActivityInfo(cn, 0);
}
final Intent intent = new Intent(Intent.ACTION_MAIN, null)
- .addCategory(Intent.CATEGORY_LAUNCHER)
- .setComponent(cn)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
- Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ .addCategory(Intent.CATEGORY_LAUNCHER)
+ .setComponent(cn)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
return addShortcut(info.loadLabel(mPackageManager).toString(),
intent, Favorites.ITEM_TYPE_APPLICATION);
@@ -393,10 +402,10 @@
mValues.put(Favorites.RESTORED, WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON);
final Intent intent = new Intent(Intent.ACTION_MAIN, null)
- .addCategory(Intent.CATEGORY_LAUNCHER)
- .setComponent(new ComponentName(packageName, className))
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
- Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ .addCategory(Intent.CATEGORY_LAUNCHER)
+ .setComponent(new ComponentName(packageName, className))
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
return addShortcut(mContext.getString(R.string.package_state_unknown), intent,
Favorites.ITEM_TYPE_APPLICATION);
}
@@ -444,7 +453,7 @@
mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
- Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
return addShortcut(mSourceRes.getString(titleResId),
intent, Favorites.ITEM_TYPE_SHORTCUT);
}
@@ -469,12 +478,22 @@
*/
protected class PendingWidgetParser implements TagParser {
- @Override
- public int parseAndAdd(XmlPullParser parser)
- throws XmlPullParserException, IOException {
+ @Nullable
+ public ComponentName getComponentName(XmlPullParser parser) {
final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
+ return null;
+ }
+ return new ComponentName(packageName, className);
+ }
+
+
+ @Override
+ public int parseAndAdd(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ ComponentName cn = getComponentName(parser);
+ if (cn == null) {
if (LOGD) Log.d(TAG, "Skipping invalid <appwidget> with no component");
return -1;
}
@@ -505,16 +524,15 @@
throw new RuntimeException("Widgets can contain only extras");
}
}
-
- return verifyAndInsert(new ComponentName(packageName, className), extras);
+ return verifyAndInsert(cn, extras);
}
protected int verifyAndInsert(ComponentName cn, Bundle extras) {
mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
mValues.put(Favorites.RESTORED,
- LauncherAppWidgetInfo.FLAG_ID_NOT_VALID |
- LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY |
- LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG);
+ LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
+ | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY
+ | LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG);
mValues.put(Favorites._ID, mCallback.generateNewItemId());
if (!extras.isEmpty()) {
mValues.put(Favorites.INTENT, new Intent().putExtras(extras).toUri(0));
@@ -529,6 +547,23 @@
}
}
+ protected class SearchWidgetParser extends PendingWidgetParser {
+ @Override
+ @Nullable
+ public ComponentName getComponentName(XmlPullParser parser) {
+ return QsbContainerView.getSearchComponentName(mContext);
+ }
+
+ @Override
+ protected int verifyAndInsert(ComponentName cn, Bundle extras) {
+ mValues.put(Favorites.OPTIONS, LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET);
+ int flags = mValues.getAsInteger(Favorites.RESTORED)
+ | WorkspaceItemInfo.FLAG_RESTORE_STARTED;
+ mValues.put(Favorites.RESTORED, flags);
+ return super.verifyAndInsert(cn, extras);
+ }
+ }
+
protected class FolderParser implements TagParser {
private final ArrayMap<String, TagParser> mFolderElements;
@@ -548,7 +583,7 @@
if (titleResId != 0) {
title = mSourceRes.getString(titleResId);
} else {
- title = mContext.getResources().getString(R.string.folder_name);
+ title = "";
}
mValues.put(Favorites.TITLE, title);
@@ -680,7 +715,8 @@
int insertAndCheck(SQLiteDatabase db, ContentValues values);
}
- @Thunk static void copyInteger(ContentValues from, ContentValues to, String key) {
+ @Thunk
+ static void copyInteger(ContentValues from, ContentValues to, String key) {
to.put(key, from.getAsInteger(key));
}
}
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 6455056..b28077f 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -27,6 +27,8 @@
import android.content.res.Configuration;
import android.view.ContextThemeWrapper;
+import androidx.annotation.IntDef;
+
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogUtils;
@@ -44,8 +46,6 @@
import java.lang.annotation.Retention;
import java.util.ArrayList;
-import androidx.annotation.IntDef;
-
public abstract class BaseActivity extends Activity
implements UserEventDelegate, LogStateProvider, ActivityContext {
@@ -265,12 +265,13 @@
}
}
- protected void dumpMisc(PrintWriter writer) {
- writer.println(" deviceProfile isTransposed=" + getDeviceProfile().isVerticalBarLayout());
- writer.println(" orientation=" + getResources().getConfiguration().orientation);
- writer.println(" mSystemUiController: " + mSystemUiController);
- writer.println(" mActivityFlags: " + mActivityFlags);
- writer.println(" mForceInvisible: " + mForceInvisible);
+ protected void dumpMisc(String prefix, PrintWriter writer) {
+ writer.println(prefix + "deviceProfile isTransposed="
+ + getDeviceProfile().isVerticalBarLayout());
+ writer.println(prefix + "orientation=" + getResources().getConfiguration().orientation);
+ writer.println(prefix + "mSystemUiController: " + mSystemUiController);
+ writer.println(prefix + "mActivityFlags: " + mActivityFlags);
+ writer.println(prefix + "mForceInvisible: " + mForceInvisible);
}
public static <T extends BaseActivity> T fromContext(Context context) {
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index f69b172..994ba65 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -30,17 +30,17 @@
import android.view.View;
import android.widget.Toast;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.uioverrides.DisplayRotationListener;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Themes;
-import androidx.annotation.Nullable;
-
/**
* Extension of BaseActivity allowing support for drag-n-drop
*/
@@ -120,6 +120,10 @@
public abstract View getRootView();
+ public void returnToHomescreen() {
+ // no-op
+ }
+
public Rect getViewBounds(View v) {
int[] pos = new int[2];
v.getLocationOnScreen(pos);
@@ -135,11 +139,7 @@
public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item,
@Nullable String sourceContainer) {
- if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_START_TAG,
- "startActivitySafely 1");
- }
- if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
+ if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) {
Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
return false;
}
@@ -162,10 +162,6 @@
startShortcutIntentSafely(intent, optsBundle, item, sourceContainer);
} else if (user == null || user.equals(Process.myUserHandle())) {
// Could be launching some bookkeeping activity
- if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_START_TAG,
- "startActivitySafely 2");
- }
startActivity(intent, optsBundle);
AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(),
Process.myUserHandle(), sourceContainer);
@@ -178,7 +174,7 @@
getUserEventDispatcher().logAppLaunch(v, intent);
getStatsLogManager().logAppLaunch(v, intent);
return true;
- } catch (ActivityNotFoundException|SecurityException e) {
+ } catch (NullPointerException|ActivityNotFoundException|SecurityException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e);
}
@@ -245,14 +241,14 @@
protected void onDeviceProfileInitiated() {
if (mDeviceProfile.isVerticalBarLayout()) {
mRotationListener.enable();
- mDeviceProfile.updateIsSeascape(getWindowManager());
+ mDeviceProfile.updateIsSeascape(this);
} else {
mRotationListener.disable();
}
}
private void onDeviceRotationChanged() {
- if (mDeviceProfile.updateIsSeascape(getWindowManager())) {
+ if (mDeviceProfile.updateIsSeascape(this)) {
reapplyUi();
}
}
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index c84be4d..864fa6e 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -22,6 +22,7 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.views.RecyclerViewFastScroller;
import androidx.recyclerview.widget.RecyclerView;
@@ -171,4 +172,13 @@
* <p>Override in each subclass of this base class.
*/
public void onFastScrollCompleted() {}
+
+ @Override
+ public void onScrollStateChanged(int state) {
+ super.onScrollStateChanged(state);
+
+ if (state == SCROLL_STATE_IDLE) {
+ AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
+ }
+ }
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 1619e36..7adb6a4 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -32,7 +32,6 @@
import android.graphics.drawable.Drawable;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.Property;
import android.util.TypedValue;
import android.view.KeyEvent;
@@ -54,8 +53,8 @@
import com.android.launcher3.icons.IconCache.IconLoadRequest;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.model.PackageItemInfo;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.IconLabelDotView;
import java.text.NumberFormat;
@@ -64,7 +63,8 @@
* because we want to make the bubble taller than the text and TextView's clip is
* too aggressive.
*/
-public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback {
+public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
+ IconLabelDotView {
private static final int DISPLAY_WORKSPACE = 0;
private static final int DISPLAY_ALL_APPS = 1;
@@ -104,6 +104,8 @@
private Drawable mIcon;
private final boolean mCenterVertically;
+ private final int mDisplay;
+
private final CheckLongPressHelper mLongPressHelper;
private final StylusEventHelper mStylusEventHelper;
private final float mSlop;
@@ -133,6 +135,9 @@
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mDisableRelayout = false;
+ @ViewDebug.ExportedProperty(category = "launcher")
+ private final boolean mIgnorePaddingTouch;
+
private IconLoadRequest mIconLoadRequest;
public BubbleTextView(Context context) {
@@ -152,26 +157,32 @@
R.styleable.BubbleTextView, defStyle, 0);
mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false);
- int display = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE);
+ mDisplay = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE);
final int defaultIconSize;
- if (display == DISPLAY_WORKSPACE) {
+ if (mDisplay == DISPLAY_WORKSPACE) {
DeviceProfile grid = mActivity.getWallpaperDeviceProfile();
setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
defaultIconSize = grid.iconSizePx;
- } else if (display == DISPLAY_ALL_APPS) {
+ mIgnorePaddingTouch = true;
+ } else if (mDisplay == DISPLAY_ALL_APPS) {
DeviceProfile grid = mActivity.getDeviceProfile();
setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
defaultIconSize = grid.allAppsIconSizePx;
- } else if (display == DISPLAY_FOLDER) {
+ mIgnorePaddingTouch = true;
+ } else if (mDisplay == DISPLAY_FOLDER) {
DeviceProfile grid = mActivity.getDeviceProfile();
setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx);
setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx);
defaultIconSize = grid.folderChildIconSizePx;
+ mIgnorePaddingTouch = true;
} else {
+ // widget_selection or shortcut_popup
defaultIconSize = mActivity.getDeviceProfile().iconSizePx;
+ mIgnorePaddingTouch = false;
}
+
mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false);
mIconSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_iconSizeOverride,
@@ -319,9 +330,15 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_START_TAG, "BubbleTextView.onTouchEvent " + event);
+ // ignore events if they happen in padding area
+ if (event.getAction() == MotionEvent.ACTION_DOWN && mIgnorePaddingTouch
+ && (event.getY() < getPaddingTop()
+ || event.getX() < getPaddingLeft()
+ || event.getY() > getHeight() - getPaddingBottom()
+ || event.getX() > getWidth() - getPaddingRight())) {
+ return false;
}
+
// Call the superclass onTouchEvent first, because sometimes it changes the state to
// isPressed() on an ACTION_UP
boolean result = super.onTouchEvent(event);
@@ -416,7 +433,8 @@
}
}
- public void forceHideDot(boolean forceHideDot) {
+ @Override
+ public void setForceHideDot(boolean forceHideDot) {
if (mForceHideDot == forceHideDot) {
return;
}
@@ -566,7 +584,11 @@
mDotInfo = mActivity.getDotInfoForItem(itemInfo);
boolean isDotted = mDotInfo != null;
float newDotScale = isDotted ? 1f : 0;
- mDotRenderer = mActivity.getDeviceProfile().mDotRenderer;
+ if (mDisplay == DISPLAY_ALL_APPS) {
+ mDotRenderer = mActivity.getDeviceProfile().mDotRendererAllApps;
+ } else {
+ mDotRenderer = mActivity.getDeviceProfile().mDotRendererWorkSpace;
+ }
if (wasDotted || isDotted) {
// Animate when a dot is first added or when it is removed.
if (animate && (wasDotted ^ isDotted) && isShown()) {
@@ -605,6 +627,7 @@
}
}
+ @Override
public void setIconVisible(boolean visible) {
mIsIconVisible = visible;
Drawable icon = visible ? mIcon : new ColorDrawable(Color.TRANSPARENT);
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 09fb244..976ccd5 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -2702,6 +2702,14 @@
}
}
+ /**
+ * Sets the position to the provided point
+ */
+ public void setXY(Point point) {
+ cellX = point.x;
+ cellY = point.y;
+ }
+
public String toString() {
return "(" + this.cellX + ", " + this.cellY + ")";
}
diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java
index 75297f6..af85594 100644
--- a/src/com/android/launcher3/DefaultLayoutParser.java
+++ b/src/com/android/launcher3/DefaultLayoutParser.java
@@ -14,13 +14,16 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
+
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.util.Thunk;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.List;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
/**
* Implements the layout parser with rules for internal layouts and partner layouts.
@@ -55,7 +58,8 @@
return getFolderElementsMap(mSourceRes);
}
- @Thunk ArrayMap<String, TagParser> getFolderElementsMap(Resources res) {
+ @Thunk
+ ArrayMap<String, TagParser> getFolderElementsMap(Resources res) {
ArrayMap<String, TagParser> parsers = new ArrayMap<>();
parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
parsers.put(TAG_SHORTCUT, new UriShortcutParser(res));
@@ -67,6 +71,7 @@
ArrayMap<String, TagParser> parsers = new ArrayMap<>();
parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
parsers.put(TAG_APPWIDGET, new AppWidgetParser());
+ parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser());
parsers.put(TAG_SHORTCUT, new UriShortcutParser(mSourceRes));
parsers.put(TAG_RESOLVE, new ResolveParser());
parsers.put(TAG_FOLDER, new MyFolderParser());
@@ -229,7 +234,8 @@
/**
* A parser which adds a folder whose contents come from partner apk.
*/
- @Thunk class PartnerFolderParser implements TagParser {
+ @Thunk
+ class PartnerFolderParser implements TagParser {
@Override
public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
@@ -255,7 +261,8 @@
/**
* An extension of FolderParser which allows adding items from a different xml.
*/
- @Thunk class MyFolderParser extends FolderParser {
+ @Thunk
+ class MyFolderParser extends FolderParser {
@Override
public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
@@ -281,7 +288,7 @@
mPackageManager.getReceiverInfo(cn, 0);
} catch (Exception e) {
String[] packages = mPackageManager.currentToCanonicalPackageNames(
- new String[] { cn.getPackageName() });
+ new String[]{cn.getPackageName()});
cn = new ComponentName(packages[0], cn.getClassName());
try {
mPackageManager.getReceiverInfo(cn, 0);
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 883e8c6..bc6fa6e 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -24,16 +24,20 @@
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.Surface;
-import android.view.WindowManager;
+
+import androidx.annotation.Nullable;
import com.android.launcher3.CellLayout.ContainerType;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
+import com.android.launcher3.util.DefaultDisplay;
public class DeviceProfile {
public final InvariantDeviceProfile inv;
+ // IDP with no grid override values.
+ @Nullable private final InvariantDeviceProfile originalIdp;
// Device properties
public final boolean isTablet;
@@ -111,6 +115,7 @@
// All apps
public int allAppsCellHeightPx;
+ public int allAppsCellWidthPx;
public int allAppsIconSizePx;
public int allAppsIconDrawablePaddingPx;
public float allAppsIconTextSizePx;
@@ -129,13 +134,15 @@
private boolean mIsSeascape;
// Notification dots
- public DotRenderer mDotRenderer;
+ public DotRenderer mDotRendererWorkSpace;
+ public DotRenderer mDotRendererAllApps;
public DeviceProfile(Context context, InvariantDeviceProfile inv,
- Point minSize, Point maxSize,
+ InvariantDeviceProfile originalIDP, Point minSize, Point maxSize,
int width, int height, boolean isLandscape, boolean isMultiWindowMode) {
this.inv = inv;
+ this.originalIdp = inv;
this.isLandscape = isLandscape;
this.isMultiWindowMode = isMultiWindowMode;
@@ -227,17 +234,33 @@
// Recalculate the available dimensions using the new hotseat size.
updateAvailableDimensions(dm, res);
}
+
+ if (originalIDP != null) {
+ // Grid size change should not affect All Apps UI, so we use the original profile
+ // measurements here.
+ DeviceProfile originalProfile = isLandscape
+ ? originalIDP.landscapeProfile
+ : originalIDP.portraitProfile;
+ allAppsIconSizePx = originalProfile.iconSizePx;
+ allAppsIconTextSizePx = originalProfile.iconTextSizePx;
+ allAppsCellHeightPx = originalProfile.allAppsCellHeightPx;
+ allAppsIconDrawablePaddingPx = originalProfile.iconDrawablePaddingOriginalPx;
+ allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
+ }
updateWorkspacePadding();
// This is done last, after iconSizePx is calculated above.
- mDotRenderer = new DotRenderer(iconSizePx, IconShape.getShapePath(),
+ mDotRendererWorkSpace = new DotRenderer(iconSizePx, IconShape.getShapePath(),
IconShape.DEFAULT_PATH_SIZE);
+ mDotRendererAllApps = iconSizePx == allAppsIconSizePx ? mDotRendererWorkSpace :
+ new DotRenderer(allAppsIconSizePx, IconShape.getShapePath(),
+ IconShape.DEFAULT_PATH_SIZE);
}
public DeviceProfile copy(Context context) {
Point size = new Point(availableWidthPx, availableHeightPx);
- return new DeviceProfile(context, inv, size, size, widthPx, heightPx, isLandscape,
- isMultiWindowMode);
+ return new DeviceProfile(context, inv, originalIdp, size, size, widthPx, heightPx,
+ isLandscape, isMultiWindowMode);
}
public DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
@@ -248,8 +271,8 @@
// In multi-window mode, we can have widthPx = availableWidthPx
// and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
// widthPx and heightPx values where it's needed.
- DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y,
- isLandscape, true);
+ DeviceProfile profile = new DeviceProfile(context, inv, originalIdp, mwSize, mwSize,
+ mwSize.x, mwSize.y, isLandscape, true);
// If there isn't enough vertical cell padding with the labels displayed, hide the labels.
float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx
@@ -307,11 +330,16 @@
updateAvailableFolderCellDimensions(dm, res);
}
+ /**
+ * Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx,
+ * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
+ * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
+ */
private void updateIconSize(float scale, Resources res, DisplayMetrics dm) {
// Workspace
final boolean isVerticalLayout = isVerticalBarLayout();
- float invIconSizePx = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
- iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizePx, dm) * scale));
+ float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
+ iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, dm) * scale));
iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale);
iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
@@ -328,13 +356,13 @@
}
cellWidthPx = iconSizePx + iconDrawablePaddingPx;
- // All apps
- allAppsIconTextSizePx = iconTextSizePx;
allAppsIconSizePx = iconSizePx;
+ allAppsIconTextSizePx = iconTextSizePx;
allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
allAppsCellHeightPx = getCellSize().y;
+ allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
- if (isVerticalLayout) {
+ if (isVerticalBarLayout()) {
// Always hide the Workspace text with vertical bar layout.
adjustToHideWorkspaceLabels();
}
@@ -375,14 +403,15 @@
Point totalWorkspacePadding = getTotalWorkspacePadding();
// Check if the icons fit within the available height.
- float usedHeight = folderCellHeightPx * inv.numFolderRows + folderBottomPanelSize;
- int maxHeight = availableHeightPx - totalWorkspacePadding.y - folderMargin;
- float scaleY = maxHeight / usedHeight;
+ float contentUsedHeight = folderCellHeightPx * inv.numFolderRows;
+ int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y - folderBottomPanelSize
+ - folderMargin;
+ float scaleY = contentMaxHeight / contentUsedHeight;
// Check if the icons fit within the available width.
- float usedWidth = folderCellWidthPx * inv.numFolderColumns;
- int maxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin;
- float scaleX = maxWidth / usedWidth;
+ float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns;
+ int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin;
+ float scaleX = contentMaxWidth / contentUsedWidth;
float scale = Math.min(scaleX, scaleY);
if (scale < 1f) {
@@ -419,14 +448,18 @@
}
public Point getCellSize() {
+ return getCellSize(inv.numColumns, inv.numRows);
+ }
+
+ private Point getCellSize(int numColumns, int numRows) {
Point result = new Point();
// Since we are only concerned with the overall padding, layout direction does
// not matter.
Point padding = getTotalWorkspacePadding();
result.x = calculateCellWidth(availableWidthPx - padding.x
- - cellLayoutPaddingLeftRightPx * 2, inv.numColumns);
+ - cellLayoutPaddingLeftRightPx * 2, numColumns);
result.y = calculateCellHeight(availableHeightPx - padding.y
- - cellLayoutBottomPaddingPx, inv.numRows);
+ - cellLayoutBottomPaddingPx, numRows);
return result;
}
@@ -542,11 +575,19 @@
}
/**
+ * Returns true when the number of workspace columns and all apps columns differs.
+ */
+ private boolean allAppsHasDifferentNumColumns() {
+ return inv.numAllAppsColumns != inv.numColumns;
+ }
+
+ /**
* Updates orientation information and returns true if it has changed from the previous value.
*/
- public boolean updateIsSeascape(WindowManager wm) {
+ public boolean updateIsSeascape(Context context) {
if (isVerticalBarLayout()) {
- boolean isSeascape = wm.getDefaultDisplay().getRotation() == Surface.ROTATION_270;
+ boolean isSeascape = DefaultDisplay.INSTANCE.get(context).getInfo().rotation
+ == Surface.ROTATION_270;
if (mIsSeascape != isSeascape) {
mIsSeascape = isSeascape;
return true;
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 7ab88a0..a90025e 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -142,8 +142,11 @@
@Override
public void setAlpha(int alpha) {
- mAlpha = alpha;
- mPaint.setAlpha(alpha);
+ if (mAlpha != alpha) {
+ mAlpha = alpha;
+ mPaint.setAlpha(alpha);
+ invalidateSelf();
+ }
}
@Override
diff --git a/src/com/android/launcher3/FirstFrameAnimatorHelper.java b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
index c967a96..6c5bc40 100644
--- a/src/com/android/launcher3/FirstFrameAnimatorHelper.java
+++ b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
@@ -15,7 +15,7 @@
*/
package com.android.launcher3;
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -108,17 +108,20 @@
// For the second frame, if the first frame took more than 16ms,
// adjust the start time and pretend it took only 16ms anyway. This
// prevents a large jump in the animation due to an expensive first frame
- } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
- !mAdjustedSecondFrameTime &&
- currentTime > mStartTime + SINGLE_FRAME_MS &&
- currentPlayTime > SINGLE_FRAME_MS) {
- animation.setCurrentPlayTime(SINGLE_FRAME_MS);
- mAdjustedSecondFrameTime = true;
} else {
- if (frameNum > 1) {
- mRootView.post(() -> animation.removeUpdateListener(this));
+ int singleFrameMS = getSingleFrameMs(mRootView.getContext());
+ if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
+ !mAdjustedSecondFrameTime &&
+ currentTime > mStartTime + singleFrameMS &&
+ currentPlayTime > singleFrameMS) {
+ animation.setCurrentPlayTime(singleFrameMS);
+ mAdjustedSecondFrameTime = true;
+ } else {
+ if (frameNum > 1) {
+ mRootView.post(() -> animation.removeUpdateListener(this));
+ }
+ if (DEBUG) print(animation);
}
- if (DEBUG) print(animation);
}
mHandlingOnAnimationUpdate = false;
} else {
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index b747d62..e2b7b68 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -50,9 +50,9 @@
/**
* The apps and shortcuts
*/
- public ArrayList<WorkspaceItemInfo> contents = new ArrayList<WorkspaceItemInfo>();
+ public ArrayList<WorkspaceItemInfo> contents = new ArrayList<>();
- ArrayList<FolderListener> listeners = new ArrayList<FolderListener>();
+ private ArrayList<FolderListener> mListeners = new ArrayList<>();
public FolderInfo() {
itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
@@ -72,10 +72,10 @@
* Add an app or shortcut for a specified rank.
*/
public void add(WorkspaceItemInfo item, int rank, boolean animate) {
- rank = Utilities.boundToRange(rank, 0, contents.size());
+ rank = Utilities.boundToRange(rank, 0, contents.size() + 1);
contents.add(rank, item);
- for (int i = 0; i < listeners.size(); i++) {
- listeners.get(i).onAdd(item, rank);
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onAdd(item, rank);
}
itemsChanged(animate);
}
@@ -87,53 +87,37 @@
*/
public void remove(WorkspaceItemInfo item, boolean animate) {
contents.remove(item);
- for (int i = 0; i < listeners.size(); i++) {
- listeners.get(i).onRemove(item);
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onRemove(item);
}
itemsChanged(animate);
}
- public void setTitle(CharSequence title) {
- this.title = title;
- for (int i = 0; i < listeners.size(); i++) {
- listeners.get(i).onTitleChanged(title);
- }
- }
-
@Override
public void onAddToDatabase(ContentWriter writer) {
super.onAddToDatabase(writer);
writer.put(LauncherSettings.Favorites.TITLE, title)
.put(LauncherSettings.Favorites.OPTIONS, options);
-
}
public void addListener(FolderListener listener) {
- listeners.add(listener);
+ mListeners.add(listener);
}
public void removeListener(FolderListener listener) {
- listeners.remove(listener);
+ mListeners.remove(listener);
}
public void itemsChanged(boolean animate) {
- for (int i = 0; i < listeners.size(); i++) {
- listeners.get(i).onItemsChanged(animate);
- }
- }
-
- public void prepareAutoUpdate() {
- for (int i = 0; i < listeners.size(); i++) {
- listeners.get(i).prepareAutoUpdate();
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onItemsChanged(animate);
}
}
public interface FolderListener {
public void onAdd(WorkspaceItemInfo item, int rank);
public void onRemove(WorkspaceItemInfo item);
- public void onTitleChanged(CharSequence title);
public void onItemsChanged(boolean animate);
- public void prepareAutoUpdate();
}
public boolean hasOption(int optionFlag) {
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 00acdcd..03ee707 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -105,8 +105,7 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
- // Don't let if follow through to workspace
- return true;
+ return event.getY() > getCellHeight();
}
@Override
diff --git a/src/com/android/launcher3/IconProvider.java b/src/com/android/launcher3/IconProvider.java
index e1ef954..0f006f7 100644
--- a/src/com/android/launcher3/IconProvider.java
+++ b/src/com/android/launcher3/IconProvider.java
@@ -1,16 +1,17 @@
package com.android.launcher3;
-import android.content.Context;
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+
import android.content.pm.LauncherActivityInfo;
import android.graphics.drawable.Drawable;
+import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.ResourceBasedOverride;
public class IconProvider implements ResourceBasedOverride {
- public static IconProvider newInstance(Context context) {
- return Overrides.getObject(IconProvider.class, context, R.string.icon_provider_class);
- }
+ public static MainThreadInitializedObject<IconProvider> INSTANCE =
+ forOverride(IconProvider.class, R.string.icon_provider_class);
public IconProvider() { }
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index e9b932a..fe91602 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -16,9 +16,12 @@
package com.android.launcher3;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -28,9 +31,6 @@
import android.content.pm.ShortcutInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
import android.os.Parcelable;
import android.os.Process;
import android.os.UserHandle;
@@ -39,6 +39,9 @@
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.icons.BitmapInfo;
@@ -65,9 +68,6 @@
public class InstallShortcutReceiver extends BroadcastReceiver {
- private static final int MSG_ADD_TO_QUEUE = 1;
- private static final int MSG_FLUSH_QUEUE = 2;
-
public static final int FLAG_ACTIVITY_PAUSED = 1;
public static final int FLAG_LOADER_RUNNING = 2;
public static final int FLAG_DRAG_AND_DROP = 4;
@@ -100,65 +100,57 @@
public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
- private static final Handler sHandler = new Handler(LauncherModel.getWorkerLooper()) {
+ @WorkerThread
+ private static void addToQueue(Context context, PendingInstallShortcutInfo info) {
+ String encoded = info.encodeToString();
+ SharedPreferences prefs = Utilities.getPrefs(context);
+ Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
+ strings = (strings != null) ? new HashSet<>(strings) : new HashSet<>(1);
+ strings.add(encoded);
+ prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
+ }
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_ADD_TO_QUEUE: {
- Pair<Context, PendingInstallShortcutInfo> pair =
- (Pair<Context, PendingInstallShortcutInfo>) msg.obj;
- String encoded = pair.second.encodeToString();
- SharedPreferences prefs = Utilities.getPrefs(pair.first);
- Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
- strings = (strings != null) ? new HashSet<>(strings) : new HashSet<String>(1);
- strings.add(encoded);
- prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
- return;
- }
- case MSG_FLUSH_QUEUE: {
- Context context = (Context) msg.obj;
- LauncherModel model = LauncherAppState.getInstance(context).getModel();
- if (model.getCallback() == null) {
- // Launcher not loaded
- return;
- }
-
- ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
- SharedPreferences prefs = Utilities.getPrefs(context);
- Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
- if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
- if (strings == null) {
- return;
- }
-
- LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
- for (String encoded : strings) {
- PendingInstallShortcutInfo info = decode(encoded, context);
- if (info == null) {
- continue;
- }
-
- String pkg = getIntentPackage(info.launchIntent);
- if (!TextUtils.isEmpty(pkg)
- && !launcherApps.isPackageEnabledForProfile(pkg, info.user)) {
- if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
- + info.launchIntent);
- continue;
- }
-
- // Generate a shortcut info to add into the model
- installQueue.add(info.getItemInfo());
- }
- prefs.edit().remove(APPS_PENDING_INSTALL).apply();
- if (!installQueue.isEmpty()) {
- model.addAndBindAddedWorkspaceItems(installQueue);
- }
- return;
- }
- }
+ @WorkerThread
+ private static void flushQueueInBackground(Context context) {
+ LauncherModel model = LauncherAppState.getInstance(context).getModel();
+ if (model.getCallback() == null) {
+ // Launcher not loaded
+ return;
}
- };
+
+ ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
+ SharedPreferences prefs = Utilities.getPrefs(context);
+ Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
+ if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
+ if (strings == null) {
+ return;
+ }
+
+ LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+ for (String encoded : strings) {
+ PendingInstallShortcutInfo info = decode(encoded, context);
+ if (info == null) {
+ continue;
+ }
+
+ String pkg = getIntentPackage(info.launchIntent);
+ if (!TextUtils.isEmpty(pkg)
+ && !launcherApps.isPackageEnabledForProfile(pkg, info.user)
+ && !info.isActivity) {
+ if (DBG) {
+ Log.d(TAG, "Ignoring shortcut for absent package: " + info.launchIntent);
+ }
+ continue;
+ }
+
+ // Generate a shortcut info to add into the model
+ installQueue.add(info.getItemInfo());
+ }
+ prefs.edit().remove(APPS_PENDING_INSTALL).apply();
+ if (!installQueue.isEmpty()) {
+ model.addAndBindAddedWorkspaceItems(installQueue);
+ }
+ }
public static void removeFromInstallQueue(Context context, HashSet<String> packageNames,
UserHandle user) {
@@ -249,10 +241,6 @@
return info == null ? null : (WorkspaceItemInfo) info.getItemInfo().first;
}
- public static WorkspaceItemInfo fromActivityInfo(LauncherActivityInfo info, Context context) {
- return (WorkspaceItemInfo) (new PendingInstallShortcutInfo(info, context).getItemInfo().first);
- }
-
public static void queueShortcut(ShortcutInfo info, Context context) {
queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context);
}
@@ -261,8 +249,9 @@
queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId, context), context);
}
- public static void queueActivityInfo(LauncherActivityInfo activity, Context context) {
- queuePendingShortcutInfo(new PendingInstallShortcutInfo(activity, context), context);
+ public static void queueApplication(Intent data, UserHandle user, Context context) {
+ queuePendingShortcutInfo(new PendingInstallShortcutInfo(data, context, user),
+ context);
}
public static HashSet<ShortcutKey> getPendingShortcuts(Context context) {
@@ -288,7 +277,7 @@
private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) {
// Queue the item up for adding if launcher has not loaded properly yet
- Message.obtain(sHandler, MSG_ADD_TO_QUEUE, Pair.create(context, info)).sendToTarget();
+ MODEL_EXECUTOR.post(() -> addToQueue(context, info));
flushInstallQueue(context);
}
@@ -304,7 +293,7 @@
if (sInstallQueueDisabledFlags != 0) {
return;
}
- Message.obtain(sHandler, MSG_FLUSH_QUEUE, context.getApplicationContext()).sendToTarget();
+ MODEL_EXECUTOR.post(() -> flushQueueInBackground(context));
}
/**
@@ -326,11 +315,11 @@
private static class PendingInstallShortcutInfo {
- final LauncherActivityInfo activityInfo;
- final ShortcutInfo shortcutInfo;
- final AppWidgetProviderInfo providerInfo;
+ final boolean isActivity;
+ @Nullable final ShortcutInfo shortcutInfo;
+ @Nullable final AppWidgetProviderInfo providerInfo;
- final Intent data;
+ @Nullable final Intent data;
final Context mContext;
final Intent launchIntent;
final String label;
@@ -340,7 +329,7 @@
* Initializes a PendingInstallShortcutInfo received from a different app.
*/
public PendingInstallShortcutInfo(Intent data, UserHandle user, Context context) {
- activityInfo = null;
+ isActivity = false;
shortcutInfo = null;
providerInfo = null;
@@ -350,18 +339,22 @@
launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
-
}
/**
* Initializes a PendingInstallShortcutInfo to represent a launcher target.
*/
public PendingInstallShortcutInfo(LauncherActivityInfo info, Context context) {
- activityInfo = info;
+ isActivity = true;
shortcutInfo = null;
providerInfo = null;
- data = null;
+ String packageName = info.getComponentName().getPackageName();
+ data = new Intent();
+ data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent().setComponent(
+ new ComponentName(packageName, "")).setPackage(packageName));
+ data.putExtra(Intent.EXTRA_SHORTCUT_NAME, info.getLabel());
+
user = info.getUser();
mContext = context;
@@ -372,8 +365,24 @@
/**
* Initializes a PendingInstallShortcutInfo to represent a launcher target.
*/
+ public PendingInstallShortcutInfo(Intent data, Context context, UserHandle user) {
+ isActivity = true;
+ shortcutInfo = null;
+ providerInfo = null;
+
+ this.data = data;
+ this.user = user;
+ mContext = context;
+
+ launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
+ label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+ }
+
+ /**
+ * Initializes a PendingInstallShortcutInfo to represent a launcher target.
+ */
public PendingInstallShortcutInfo(ShortcutInfo info, Context context) {
- activityInfo = null;
+ isActivity = false;
shortcutInfo = info;
providerInfo = null;
@@ -390,7 +399,7 @@
*/
public PendingInstallShortcutInfo(
AppWidgetProviderInfo info, int widgetId, Context context) {
- activityInfo = null;
+ isActivity = false;
shortcutInfo = null;
providerInfo = info;
@@ -405,17 +414,7 @@
public String encodeToString() {
try {
- if (activityInfo != null) {
- // If it a launcher target, we only need component name, and user to
- // recreate this.
- return new JSONStringer()
- .object()
- .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
- .key(APP_SHORTCUT_TYPE_KEY).value(true)
- .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
- .getSerialNumberForUser(user))
- .endObject().toString();
- } else if (shortcutInfo != null) {
+ if (shortcutInfo != null) {
// If it a launcher target, we only need component name, and user to
// recreate this.
return new JSONStringer()
@@ -449,20 +448,26 @@
// This name is only used for comparisons and notifications, so fall back to activity
// name if not supplied
String name = ensureValidName(mContext, launchIntent, label).toString();
- Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
- Intent.ShortcutIconResource iconResource =
- data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+ Bitmap icon = data == null ? null
+ : data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+ Intent.ShortcutIconResource iconResource = data == null ? null
+ : data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
// Only encode the parameters which are supported by the API.
JSONStringer json = new JSONStringer()
.object()
.key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
- .key(NAME_KEY).value(name);
+ .key(NAME_KEY).value(name)
+ .key(USER_HANDLE_KEY).value(
+ UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user))
+ .key(APP_SHORTCUT_TYPE_KEY).value(isActivity);
if (icon != null) {
byte[] iconByteArray = GraphicsUtils.flattenBitmap(icon);
- json = json.key(ICON_KEY).value(
- Base64.encodeToString(
- iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
+ if (iconByteArray != null) {
+ json = json.key(ICON_KEY).value(
+ Base64.encodeToString(
+ iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
+ }
}
if (iconResource != null) {
json = json.key(ICON_RESOURCE_NAME_KEY).value(iconResource.resourceName);
@@ -477,29 +482,18 @@
}
public Pair<ItemInfo, Object> getItemInfo() {
- if (activityInfo != null) {
- AppInfo appInfo = new AppInfo(mContext, activityInfo, user);
- final LauncherAppState app = LauncherAppState.getInstance(mContext);
- // Set default values until proper values is loaded.
- appInfo.title = "";
- appInfo.applyFrom(app.getIconCache().getDefaultIcon(user));
- final WorkspaceItemInfo si = appInfo.makeWorkspaceItem();
- if (Looper.myLooper() == LauncherModel.getWorkerLooper()) {
- app.getIconCache().getTitleAndIcon(si, activityInfo, false /* useLowResIcon */);
- } else {
- app.getModel().updateAndBindWorkspaceItem(() -> {
- app.getIconCache().getTitleAndIcon(
- si, activityInfo, false /* useLowResIcon */);
- return si;
- });
- }
- return Pair.create((ItemInfo) si, (Object) activityInfo);
+ if (isActivity) {
+ WorkspaceItemInfo si = createWorkspaceItemInfo(data, user,
+ LauncherAppState.getInstance(mContext));
+ si.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+ si.status |= WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
+ return Pair.create(si, null);
} else if (shortcutInfo != null) {
- WorkspaceItemInfo si = new WorkspaceItemInfo(shortcutInfo, mContext);
+ WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(shortcutInfo, mContext);
LauncherIcons li = LauncherIcons.obtain(mContext);
- si.applyFrom(li.createShortcutIcon(shortcutInfo));
+ itemInfo.applyFrom(li.createShortcutIcon(shortcutInfo));
li.recycle();
- return Pair.create((ItemInfo) si, (Object) shortcutInfo);
+ return Pair.create(itemInfo, shortcutInfo);
} else if (providerInfo != null) {
LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
.fromProviderInfo(mContext, providerInfo);
@@ -511,15 +505,16 @@
widgetInfo.minSpanY = info.minSpanY;
widgetInfo.spanX = Math.min(info.spanX, idp.numColumns);
widgetInfo.spanY = Math.min(info.spanY, idp.numRows);
- return Pair.create((ItemInfo) widgetInfo, (Object) providerInfo);
+ return Pair.create(widgetInfo, providerInfo);
} else {
- WorkspaceItemInfo si = createWorkspaceItemInfo(data, LauncherAppState.getInstance(mContext));
- return Pair.create((ItemInfo) si, null);
+ WorkspaceItemInfo itemInfo =
+ createWorkspaceItemInfo(data, user, LauncherAppState.getInstance(mContext));
+ return Pair.create(itemInfo, null);
}
}
public boolean isLauncherActivity() {
- return activityInfo != null;
+ return isActivity;
}
}
@@ -534,7 +529,9 @@
if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
LauncherActivityInfo info = LauncherAppsCompat.getInstance(context)
.resolveActivity(decoder.launcherIntent, decoder.user);
- return info == null ? null : new PendingInstallShortcutInfo(info, context);
+ if (info != null) {
+ return new PendingInstallShortcutInfo(info, context);
+ }
} else if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) {
DeepShortcutManager sm = DeepShortcutManager.getInstance(context);
List<ShortcutInfo> si = sm.queryForFullDetails(
@@ -578,7 +575,11 @@
data.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
}
- return new PendingInstallShortcutInfo(data, decoder.user, context);
+ if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
+ return new PendingInstallShortcutInfo(data, context, decoder.user);
+ } else {
+ return new PendingInstallShortcutInfo(data, decoder.user, context);
+ }
} catch (JSONException | URISyntaxException e) {
Log.d(TAG, "Exception reading shortcut to add: " + e);
}
@@ -612,7 +613,7 @@
// Already an activity target
return original;
}
- if (!Utilities.isLauncherAppTarget(original.launchIntent)) {
+ if (!PackageManagerHelper.isLauncherAppTarget(original.launchIntent)) {
return original;
}
@@ -625,7 +626,13 @@
return new PendingInstallShortcutInfo(info, original.mContext);
}
- private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, LauncherAppState app) {
+ private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, UserHandle user,
+ LauncherAppState app) {
+ if (data == null) {
+ Log.e(TAG, "Can't construct WorkspaceItemInfo with null data");
+ return null;
+ }
+
Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
@@ -637,10 +644,7 @@
}
final WorkspaceItemInfo info = new WorkspaceItemInfo();
-
- // Only support intents for current user for now. Intents sent from other
- // users wouldn't get here without intent forwarding anyway.
- info.user = Process.myUserHandle();
+ info.user = user;
BitmapInfo iconInfo = null;
LauncherIcons li = LauncherIcons.obtain(app.getContext());
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index bde87cb..d66ba73 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -18,6 +18,8 @@
import static com.android.launcher3.Utilities.getDevicePrefs;
import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.settings.SettingsActivity.GRID_OPTIONS_PREFERENCE_KEY;
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
import android.annotation.TargetApi;
@@ -39,11 +41,13 @@
import android.util.SparseArray;
import android.util.TypedValue;
import android.util.Xml;
-import android.view.Display;
-import android.view.WindowManager;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.util.ConfigMonitor;
+import com.android.launcher3.util.DefaultDisplay;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Themes;
@@ -54,9 +58,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
+import java.util.Comparator;
public class InvariantDeviceProfile {
@@ -102,6 +104,8 @@
public int iconBitmapSize;
public int fillResIconDpi;
public float iconTextSize;
+ public float allAppsIconSize;
+ public float allAppsIconTextSize;
private SparseArray<TypedValue> mExtraAttrs;
@@ -110,6 +114,11 @@
*/
public int numHotseatIcons;
+ /**
+ * Number of columns in the all apps list.
+ */
+ public int numAllAppsColumns;
+
public int defaultLayoutId;
int demoModeLayoutId;
@@ -136,6 +145,9 @@
landscapeIconSize = p.landscapeIconSize;
iconTextSize = p.iconTextSize;
numHotseatIcons = p.numHotseatIcons;
+ numAllAppsColumns = p.numAllAppsColumns;
+ allAppsIconSize = p.allAppsIconSize;
+ allAppsIconTextSize = p.allAppsIconTextSize;
defaultLayoutId = p.defaultLayoutId;
demoModeLayoutId = p.demoModeLayoutId;
mExtraAttrs = p.mExtraAttrs;
@@ -144,7 +156,10 @@
@TargetApi(23)
private InvariantDeviceProfile(Context context) {
- initGrid(context, Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null));
+ String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
+ ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
+ : null;
+ initGrid(context, gridName);
mConfigMonitor = new ConfigMonitor(context,
APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
mOverlayMonitor = new OverlayMonitor(context);
@@ -172,63 +187,81 @@
}
private String initGrid(Context context, String gridName) {
- WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- Display display = wm.getDefaultDisplay();
- DisplayMetrics dm = new DisplayMetrics();
- display.getMetrics(dm);
+ DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(context).getInfo();
- Point smallestSize = new Point();
- Point largestSize = new Point();
- display.getCurrentSizeRange(smallestSize, largestSize);
+ Point smallestSize = new Point(displayInfo.smallestSize);
+ Point largestSize = new Point(displayInfo.largestSize);
- ArrayList<DisplayOption> allOptions = getPredefinedDeviceProfiles(context, gridName);
// This guarantees that width < height
- float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm);
- float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm);
- // Sort the profiles based on the closeness to the device size
- Collections.sort(allOptions, (a, b) ->
- Float.compare(dist(minWidthDps, minHeightDps, a.minWidthDps, a.minHeightDps),
- dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps)));
- DisplayOption interpolatedDisplayOption =
- invDistWeightedInterpolate(minWidthDps, minHeightDps, allOptions);
+ float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y),
+ displayInfo.metrics);
+ float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y),
+ displayInfo.metrics);
- GridOption closestProfile = allOptions.get(0).grid;
- numRows = closestProfile.numRows;
- numColumns = closestProfile.numColumns;
- numHotseatIcons = closestProfile.numHotseatIcons;
- defaultLayoutId = closestProfile.defaultLayoutId;
- demoModeLayoutId = closestProfile.demoModeLayoutId;
- numFolderRows = closestProfile.numFolderRows;
- numFolderColumns = closestProfile.numFolderColumns;
- mExtraAttrs = closestProfile.extraAttrs;
-
- if (!closestProfile.name.equals(gridName)) {
- Utilities.getPrefs(context).edit()
- .putString(KEY_IDP_GRID_NAME, closestProfile.name).apply();
- }
-
- iconSize = interpolatedDisplayOption.iconSize;
- iconShapePath = getIconShapePath(context);
- landscapeIconSize = interpolatedDisplayOption.landscapeIconSize;
- iconBitmapSize = ResourceUtils.pxFromDp(iconSize, dm);
- iconTextSize = interpolatedDisplayOption.iconTextSize;
- fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
-
- // If the partner customization apk contains any grid overrides, apply them
- // Supported overrides: numRows, numColumns, iconSize
- applyPartnerDeviceProfileOverrides(context, dm);
-
- Point realSize = new Point();
- display.getRealSize(realSize);
+ Point realSize = new Point(displayInfo.realSize);
// The real size never changes. smallSide and largeSide will remain the
// same in any orientation.
int smallSide = Math.min(realSize.x, realSize.y);
int largeSide = Math.max(realSize.x, realSize.y);
- landscapeProfile = new DeviceProfile(context, this, smallestSize, largestSize,
- largeSide, smallSide, true /* isLandscape */, false /* isMultiWindowMode */);
- portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize,
- smallSide, largeSide, false /* isLandscape */, false /* isMultiWindowMode */);
+ // We want a list of all options as well as the list of filtered options. This allows us
+ // to have a consistent UI for areas that the grid size change should not affect
+ // ie. All Apps should be consistent between grid sizes.
+ ArrayList<DisplayOption> allOptions = new ArrayList<>();
+ ArrayList<DisplayOption> filteredOptions = new ArrayList<>();
+ getPredefinedDeviceProfiles(context, gridName, filteredOptions, allOptions);
+
+ if (allOptions.isEmpty() && filteredOptions.isEmpty()) {
+ throw new RuntimeException("No display option with canBeDefault=true");
+ }
+
+ // Sort the profiles based on the closeness to the device size
+ Comparator<DisplayOption> comparator = (a, b) -> Float.compare(dist(minWidthDps,
+ minHeightDps, a.minWidthDps, a.minHeightDps),
+ dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps));
+
+ // Calculate the device profiles as if there is no grid override.
+ Collections.sort(allOptions, comparator);
+ DisplayOption interpolatedDisplayOption =
+ invDistWeightedInterpolate(minWidthDps, minHeightDps, allOptions);
+ initGridOption(context, allOptions, interpolatedDisplayOption, displayInfo.metrics);
+
+ // Create IDP with no grid override values.
+ InvariantDeviceProfile originalIDP = new InvariantDeviceProfile(this);
+ originalIDP.landscapeProfile = new DeviceProfile(context, this, null, smallestSize,
+ largestSize, largeSide, smallSide, true /* isLandscape */,
+ false /* isMultiWindowMode */);
+ originalIDP.portraitProfile = new DeviceProfile(context, this, null, smallestSize,
+ largestSize, smallSide, largeSide, false /* isLandscape */,
+ false /* isMultiWindowMode */);
+
+ if (filteredOptions.isEmpty()) {
+ filteredOptions = allOptions;
+
+ landscapeProfile = originalIDP.landscapeProfile;
+ portraitProfile = originalIDP.portraitProfile;
+ } else {
+ Collections.sort(filteredOptions, comparator);
+ interpolatedDisplayOption =
+ invDistWeightedInterpolate(minWidthDps, minHeightDps, filteredOptions);
+
+ initGridOption(context, filteredOptions, interpolatedDisplayOption,
+ displayInfo.metrics);
+ numAllAppsColumns = originalIDP.numAllAppsColumns;
+
+ landscapeProfile = new DeviceProfile(context, this, originalIDP, smallestSize,
+ largestSize, largeSide, smallSide, true /* isLandscape */,
+ false /* isMultiWindowMode */);
+ portraitProfile = new DeviceProfile(context, this, originalIDP, smallestSize,
+ largestSize, smallSide, largeSide, false /* isLandscape */,
+ false /* isMultiWindowMode */);
+ }
+
+ GridOption closestProfile = filteredOptions.get(0).grid;
+ if (!closestProfile.name.equals(gridName)) {
+ Utilities.getPrefs(context).edit()
+ .putString(KEY_IDP_GRID_NAME, closestProfile.name).apply();
+ }
// We need to ensure that there is enough extra space in the wallpaper
// for the intended parallax effects
@@ -246,6 +279,33 @@
return closestProfile.name;
}
+ private void initGridOption(Context context, ArrayList<DisplayOption> options,
+ DisplayOption displayOption, DisplayMetrics metrics) {
+ GridOption closestProfile = options.get(0).grid;
+ numRows = closestProfile.numRows;
+ numColumns = closestProfile.numColumns;
+ numHotseatIcons = closestProfile.numHotseatIcons;
+ defaultLayoutId = closestProfile.defaultLayoutId;
+ demoModeLayoutId = closestProfile.demoModeLayoutId;
+ numFolderRows = closestProfile.numFolderRows;
+ numFolderColumns = closestProfile.numFolderColumns;
+ numAllAppsColumns = numColumns;
+
+ mExtraAttrs = closestProfile.extraAttrs;
+
+ iconSize = displayOption.iconSize;
+ iconShapePath = getIconShapePath(context);
+ landscapeIconSize = displayOption.landscapeIconSize;
+ iconBitmapSize = ResourceUtils.pxFromDp(iconSize, metrics);
+ iconTextSize = displayOption.iconTextSize;
+ fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
+
+ // If the partner customization apk contains any grid overrides, apply them
+ // Supported overrides: numRows, numColumns, iconSize
+ applyPartnerDeviceProfileOverrides(context, metrics);
+ }
+
+
@Nullable
public TypedValue getAttrValue(int attr) {
return mExtraAttrs == null ? null : mExtraAttrs.get(attr);
@@ -280,7 +340,7 @@
public void setCurrentGrid(Context context, String gridName) {
Context appContext = context.getApplicationContext();
Utilities.getPrefs(appContext).edit().putString(KEY_IDP_GRID_NAME, gridName).apply();
- new MainThreadExecutor().execute(() -> onConfigChanged(appContext));
+ MAIN_EXECUTOR.execute(() -> onConfigChanged(appContext));
}
private void onConfigChanged(Context context) {
@@ -288,9 +348,10 @@
InvariantDeviceProfile oldProfile = new InvariantDeviceProfile(this);
// Re-init grid
- // TODO(b/131867841): We pass in null here so that we can calculate the closest profile
- // without the bias of the grid name.
- initGrid(context, null);
+ String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
+ ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
+ : null;
+ initGrid(context, gridName);
int changeFlags = 0;
if (numRows != oldProfile.numRows ||
@@ -322,7 +383,13 @@
}
}
- static ArrayList<DisplayOption> getPredefinedDeviceProfiles(Context context, String gridName) {
+ /**
+ * @param gridName The current grid name.
+ * @param filteredOptionsOut List filled with all the filtered options based on gridName.
+ * @param allOptionsOut List filled with all the options that can be the default option.
+ */
+ static void getPredefinedDeviceProfiles(Context context, String gridName,
+ ArrayList<DisplayOption> filteredOptionsOut, ArrayList<DisplayOption> allOptionsOut) {
ArrayList<DisplayOption> profiles = new ArrayList<>();
try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
final int depth = parser.getDepth();
@@ -349,26 +416,19 @@
throw new RuntimeException(e);
}
- ArrayList<DisplayOption> filteredProfiles = new ArrayList<>();
if (!TextUtils.isEmpty(gridName)) {
for (DisplayOption option : profiles) {
if (gridName.equals(option.grid.name)) {
- filteredProfiles.add(option);
+ filteredOptionsOut.add(option);
}
}
}
- if (filteredProfiles.isEmpty()) {
- // No grid found, use the default options
- for (DisplayOption option : profiles) {
- if (option.canBeDefault) {
- filteredProfiles.add(option);
- }
+
+ for (DisplayOption option : profiles) {
+ if (option.canBeDefault) {
+ allOptionsOut.add(option);
}
}
- if (filteredProfiles.isEmpty()) {
- throw new RuntimeException("No display option with canBeDefault=true");
- }
- return filteredProfiles;
}
private int getLauncherIconDensity(int requiredSize) {
@@ -514,6 +574,7 @@
R.styleable.GridDisplayOption_numFolderRows, numRows);
numFolderColumns = a.getInt(
R.styleable.GridDisplayOption_numFolderColumns, numColumns);
+
a.recycle();
extraAttrs = Themes.createValueMap(context, attrs,
@@ -530,8 +591,8 @@
private final boolean canBeDefault;
private float iconSize;
- private float landscapeIconSize;
private float iconTextSize;
+ private float landscapeIconSize;
DisplayOption(GridOption grid, Context context, AttributeSet attrs) {
this.grid = grid;
@@ -549,6 +610,7 @@
landscapeIconSize = a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconSize,
iconSize);
iconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0);
+
a.recycle();
}
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 134e116..3f723d1 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -22,6 +22,8 @@
import android.os.Process;
import android.os.UserHandle;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.util.ContentWriter;
/**
@@ -134,6 +136,7 @@
return null;
}
+ @Nullable
public ComponentName getTargetComponent() {
Intent intent = getIntent();
if (intent != null) {
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
index e29f927..1550bb0 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -27,6 +27,8 @@
*/
public abstract class ItemInfoWithIcon extends ItemInfo {
+ public static final String TAG = "ItemInfoDebug";
+
/**
* A bitmap version of the application icon.
*/
@@ -126,4 +128,8 @@
iconColor = info.color;
}
+ /**
+ * @return a copy of this
+ */
+ public abstract ItemInfoWithIcon clone();
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index bc3aa7e..1338b30 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -16,10 +16,11 @@
package com.android.launcher3;
-import static android.content.pm.ActivityInfo.CONFIG_LOCALE;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherState.ALL_APPS;
@@ -77,6 +78,9 @@
import android.view.animation.OvershootInterpolator;
import android.widget.Toast;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.AllAppsContainerView;
@@ -91,9 +95,9 @@
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderGridOrganizer;
import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.folder.FolderIconPreviewVerifier;
+import com.android.launcher3.folder.FolderNameProvider;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.keyboard.CustomActionsPopup;
@@ -103,14 +107,14 @@
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate;
import com.android.launcher3.model.AppLaunchTracker;
+import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupDataProvider;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.qsb.QsbContainerView;
import com.android.launcher3.states.InternalStateHandler;
import com.android.launcher3.states.RotationHelper;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -127,6 +131,7 @@
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.PendingRequestArgs;
import com.android.launcher3.util.RaceConditionTracker;
+import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
@@ -144,7 +149,7 @@
import com.android.launcher3.widget.WidgetHostViewLoader;
import com.android.launcher3.widget.WidgetListRowEntry;
import com.android.launcher3.widget.WidgetsFullSheet;
-import com.android.launcher3.widget.custom.CustomWidgetParser;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -155,13 +160,11 @@
import java.util.List;
import java.util.function.Predicate;
-import androidx.annotation.Nullable;
-
/**
* Default launcher application.
*/
public class Launcher extends BaseDraggingActivity implements LauncherExterns,
- LauncherModel.Callbacks, LauncherProviderChangeListener, UserEventDelegate,
+ Callbacks, LauncherProviderChangeListener, UserEventDelegate,
InvariantDeviceProfile.OnIDPChangeListener {
public static final String TAG = "Launcher";
static final boolean LOGD = false;
@@ -208,9 +211,9 @@
private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
// How long to wait before the new-shortcut animation automatically pans the workspace
- private static final int NEW_APPS_PAGE_MOVE_DELAY = 500;
+ @VisibleForTesting public static final int NEW_APPS_PAGE_MOVE_DELAY = 500;
private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
- @Thunk static final int NEW_APPS_ANIMATION_DELAY = 500;
+ @Thunk @VisibleForTesting public static final int NEW_APPS_ANIMATION_DELAY = 500;
private static final int APPS_VIEW_ALPHA_CHANNEL_INDEX = 1;
private static final int SCRIM_VIEW_ALPHA_CHANNEL_INDEX = 0;
@@ -218,9 +221,11 @@
private LauncherAppTransitionManager mAppTransitionManager;
private Configuration mOldConfig;
- @Thunk Workspace mWorkspace;
+ @Thunk
+ Workspace mWorkspace;
private View mLauncherView;
- @Thunk DragLayer mDragLayer;
+ @Thunk
+ DragLayer mDragLayer;
private DragController mDragController;
private AppWidgetManagerCompat mAppWidgetManager;
@@ -228,21 +233,25 @@
private final int[] mTmpAddItemCellCoordinates = new int[2];
- @Thunk Hotseat mHotseat;
+ @Thunk
+ Hotseat mHotseat;
private DropTargetBar mDropTargetBar;
// Main container view for the all apps screen.
- @Thunk AllAppsContainerView mAppsView;
+ @Thunk
+ AllAppsContainerView mAppsView;
AllAppsTransitionController mAllAppsController;
// Scrim view for the all apps and overview state.
- @Thunk ScrimView mScrimView;
+ @Thunk
+ ScrimView mScrimView;
// UI and state for the overview panel
private View mOverviewPanel;
- @Thunk boolean mWorkspaceLoading = true;
+ @Thunk
+ boolean mWorkspaceLoading = true;
private ArrayList<OnResumeCallback> mOnResumeCallbacks = new ArrayList<>();
@@ -324,8 +333,8 @@
UiFactory.onCreate(this);
mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
-
- mAppWidgetHost = new LauncherAppWidgetHost(this);
+ mAppWidgetHost = new LauncherAppWidgetHost(this,
+ appWidgetId -> getWorkspace().removeWidget(appWidgetId));
mAppWidgetHost.startListening();
mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);
@@ -387,7 +396,8 @@
RaceConditionTracker.onEvent(ON_CREATE_EVT, EXIT);
mStateManager.addStateListener(new LauncherStateManager.StateListener() {
@Override
- public void onStateTransitionStart(LauncherState toState) {}
+ public void onStateTransitionStart(LauncherState toState) {
+ }
@Override
public void onStateTransitionComplete(LauncherState finalState) {
@@ -417,10 +427,6 @@
public void onConfigurationChanged(Configuration newConfig) {
int diff = newConfig.diff(mOldConfig);
- if ((diff & CONFIG_LOCALE) != 0) {
- Folder.setLocaleDependentFields(getResources(), true /* force */);
- }
-
if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
onIdpChanged(mDeviceProfile.inv);
}
@@ -440,12 +446,16 @@
@Override
public void reapplyUi() {
+ reapplyUi(true /* cancelCurrentAnimation */);
+ }
+
+ public void reapplyUi(boolean cancelCurrentAnimation) {
if (supportsFakeLandscapeUI()) {
mRotationMode = mStableDeviceProfile == null
? RotationMode.NORMAL : UiFactory.getRotationMode(mDeviceProfile);
}
getRootView().dispatchInsets();
- getStateManager().reapplyState(true /* cancelCurrentAnimation */);
+ getStateManager().reapplyState(cancelCurrentAnimation);
}
@Override
@@ -495,6 +505,8 @@
// Load configuration-specific DeviceProfile
mDeviceProfile = idp.getDeviceProfile(this);
if (isInMultiWindowMode()) {
+ // Note: Calls to getSize() can't rely on our cached DefaultDisplay since it can return
+ // the app window size
Display display = getWindowManager().getDefaultDisplay();
Point mwSize = new Point();
display.getSize(mwSize);
@@ -546,6 +558,10 @@
return mStateManager;
}
+ public FolderNameProvider getFolderNameProvider() {
+ return new FolderNameProvider();
+ }
+
@Override
public <T extends View> T findViewById(int id) {
return mLauncherView.findViewById(id);
@@ -602,10 +618,9 @@
if (info.container >= 0) {
View folderIcon = getWorkspace().getHomescreenIconByItemId(info.container);
if (folderIcon instanceof FolderIcon && folderIcon.getTag() instanceof FolderInfo) {
- FolderIconPreviewVerifier verifier =
- new FolderIconPreviewVerifier(getDeviceProfile().inv);
- verifier.setFolderInfo((FolderInfo) folderIcon.getTag());
- if (verifier.isItemInPreview(info.rank)) {
+ if (new FolderGridOrganizer(getDeviceProfile().inv)
+ .setFolderInfo((FolderInfo) folderIcon.getTag())
+ .isItemInPreview(info.rank)) {
folderIcon.invalidate();
}
}
@@ -645,7 +660,8 @@
.getLauncherAppWidgetInfo(widgetId);
if (provider != null) {
new WidgetAddFlowHandler(provider)
- .startConfigActivity(this, widgetInfo, REQUEST_RECONFIGURE_APPWIDGET);
+ .startConfigActivity(this, widgetInfo,
+ REQUEST_RECONFIGURE_APPWIDGET);
}
}
break;
@@ -833,7 +849,8 @@
}
}
- @Thunk void completeTwoStageWidgetDrop(
+ @Thunk
+ void completeTwoStageWidgetDrop(
final int resultCode, final int appWidgetId, final PendingRequestArgs requestArgs) {
CellLayout cellLayout = mWorkspace.getScreenWithId(requestArgs.screenId);
Runnable onCompleteRunnable = null;
@@ -873,9 +890,7 @@
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onStop();
}
-
- getUserEventDispatcher().logActionCommand(Action.Command.STOP,
- mStateManager.getState().containerType, -1);
+ logStopAndResume(Action.Command.STOP);
mAppWidgetHost.setListenIfResumed(false);
@@ -890,9 +905,6 @@
@Override
protected void onStart() {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "Launcher.onStart");
- }
RaceConditionTracker.onEvent(ON_START_EVT, ENTER);
super.onStart();
if (mLauncherCallbacks != null) {
@@ -904,8 +916,7 @@
private void handleDeferredResume() {
if (hasBeenResumed() && !mStateManager.getState().disableInteraction) {
- getUserEventDispatcher().logActionCommand(Action.Command.RESUME,
- mStateManager.getState().containerType, -1);
+ logStopAndResume(Action.Command.RESUME);
getUserEventDispatcher().startSession();
UiFactory.onLauncherStateOrResumeChanged(this);
@@ -932,8 +943,18 @@
}
}
- protected void onStateSet(LauncherState state) {
- getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
+ private void logStopAndResume(int command) {
+ int containerType = mStateManager.getState().containerType;
+ if (containerType == ContainerType.WORKSPACE && mWorkspace != null) {
+ getUserEventDispatcher().logActionCommand(command,
+ containerType, -1, mWorkspace.isOverlayShown() ? -1 : 0);
+ } else {
+ getUserEventDispatcher().logActionCommand(command, containerType, -1);
+ }
+
+ }
+
+ public void onStateSetStart(LauncherState state) {
if (mDeferredResumePending) {
handleDeferredResume();
}
@@ -942,6 +963,12 @@
}
}
+ public void onStateSetEnd(LauncherState state) {
+ getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
+ getWorkspace().setClipChildren(!state.disablePageClipping);
+ finishAutoCancelActionMode();
+ }
+
@Override
protected void onResume() {
RaceConditionTracker.onEvent(ON_RESUME_EVT, ENTER);
@@ -1054,7 +1081,8 @@
mStateManager.goToState(state, false /* animated */);
}
- PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS);
+ PendingRequestArgs requestArgs = savedState.getParcelable(
+ RUNTIME_STATE_PENDING_REQUEST_ARGS);
if (requestArgs != null) {
setWaitingForResult(requestArgs);
}
@@ -1124,8 +1152,7 @@
* Creates a view representing a shortcut inflated from the specified resource.
*
* @param parent The group the shortcut belongs to.
- * @param info The data structure describing the shortcut.
- *
+ * @param info The data structure describing the shortcut.
* @return A View inflated from layoutResId.
*/
public View createShortcut(ViewGroup parent, WorkspaceItemInfo info) {
@@ -1227,7 +1254,8 @@
*
* @param appWidgetId The app widget id
*/
- @Thunk void completeAddAppWidget(int appWidgetId, ItemInfo itemInfo,
+ @Thunk
+ void completeAddAppWidget(int appWidgetId, ItemInfo itemInfo,
AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) {
if (appWidgetInfo == null) {
@@ -1345,7 +1373,9 @@
return mSharedPrefs;
}
- public int getOrientation() { return mOldConfig.orientation; }
+ public int getOrientation() {
+ return mOldConfig.orientation;
+ }
@Override
protected void onNewIntent(Intent intent) {
@@ -1367,6 +1397,7 @@
if (!internalStateHandled) {
// In all these cases, only animate if we're already on home
AbstractFloatingView.closeAllOpenViews(this, isStarted());
+ UiFactory.closeSystemWindows();
if (!isInState(NORMAL)) {
// Only change state, if not already the same. This prevents cancelling any
@@ -1429,9 +1460,10 @@
outState.remove(RUNTIME_STATE_WIDGET_PANEL);
}
- // We close any open folders and shortcut containers since they will not be re-opened,
+ // We close any open folders and shortcut containers that are not safe for rebind,
// and we need to make sure this state is reflected.
- AbstractFloatingView.closeAllOpenViews(this, false);
+ AbstractFloatingView.closeOpenViews(this, false, TYPE_ALL & ~TYPE_REBIND_SAFE);
+ finishAutoCancelActionMode();
if (mPendingRequestArgs != null) {
outState.putParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS, mPendingRequestArgs);
@@ -1568,7 +1600,8 @@
void addAppWidgetImpl(int appWidgetId, ItemInfo info,
AppWidgetHostView boundWidget, WidgetAddFlowHandler addFlowHandler, int delay) {
- if (!addFlowHandler.startConfigActivity(this, appWidgetId, info, REQUEST_CREATE_APPWIDGET)) {
+ if (!addFlowHandler.startConfigActivity(this, appWidgetId, info,
+ REQUEST_CREATE_APPWIDGET)) {
// If the configuration flow was not started, add the widget
Runnable onComplete = new Runnable() {
@@ -1578,7 +1611,8 @@
mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
}
};
- completeAddAppWidget(appWidgetId, info, boundWidget, addFlowHandler.getProviderInfo(this));
+ completeAddAppWidget(appWidgetId, info, boundWidget,
+ addFlowHandler.getProviderInfo(this));
mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
}
}
@@ -1604,7 +1638,7 @@
break;
default:
throw new IllegalStateException("Unknown item type: " + info.itemType);
- }
+ }
}
/**
@@ -1640,10 +1674,9 @@
} else {
// In this case, we either need to start an activity to get permission to bind
// the widget, or we need to start an activity to configure the widget, or both.
- if (FeatureFlags.ENABLE_CUSTOM_WIDGETS &&
- info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET) {
- appWidgetId = CustomWidgetParser.getWidgetIdForCustomProvider(
- this, info.componentName);
+ if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET) {
+ appWidgetId = CustomWidgetManager.INSTANCE.get(this).getWidgetIdForCustomProvider(
+ info.componentName);
} else {
appWidgetId = getAppWidgetHost().allocateAppWidgetId();
}
@@ -1662,7 +1695,7 @@
FolderIcon addFolder(CellLayout layout, int container, final int screenId, int cellX,
int cellY) {
final FolderInfo folderInfo = new FolderInfo();
- folderInfo.title = getText(R.string.folder_name);
+ folderInfo.title = "";
// Update the model
getModelWriter().addItemToDatabase(folderInfo, container, screenId, cellX, cellY);
@@ -1716,8 +1749,6 @@
return true;
}
-
-
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event);
@@ -1781,7 +1812,7 @@
@Override
public int getCurrentState() {
- if(mStateManager.getState() == LauncherState.ALL_APPS) {
+ if (mStateManager.getState() == LauncherState.ALL_APPS) {
return StatsLogUtils.LAUNCHER_STATE_ALLAPPS;
} else if (mStateManager.getState() == OVERVIEW) {
return StatsLogUtils.LAUNCHER_STATE_OVERVIEW;
@@ -1809,11 +1840,6 @@
public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
@Nullable String sourceContainer) {
- if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_START_TAG,
- "startActivitySafely outer");
- }
-
if (!hasBeenResumed()) {
// Workaround an issue where the WM launch animation is clobbered when finishing the
// recents animation into launcher. Defer launching the activity until Launcher is
@@ -1904,6 +1930,10 @@
if (mPendingExecutor != null) {
mPendingExecutor.markCompleted();
mPendingExecutor = null;
+
+ // We might have set this flag previously and forgot to clear it.
+ mAppsView.getAppsStore()
+ .disableDeferUpdatesSilently(AllAppsStore.DEFER_UPDATES_NEXT_DRAW);
}
}
@@ -1917,8 +1947,7 @@
// Floating panels (except the full widget sheet) are associated with individual icons. If
// we are starting a fresh bind, close all such panels as all the icons are about
// to go away.
- AbstractFloatingView.closeOpenViews(this, true,
- AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
+ AbstractFloatingView.closeOpenViews(this, true, TYPE_ALL & ~TYPE_REBIND_SAFE);
setWorkspaceLoading(true);
@@ -2046,7 +2075,7 @@
throw new RuntimeException("Invalid Item Type");
}
- /*
+ /*
* Remove colliding items.
*/
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
@@ -2118,6 +2147,14 @@
}
private View inflateAppWidget(LauncherAppWidgetInfo item) {
+ if (item.hasOptionFlag(LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET)) {
+ item.providerName = QsbContainerView.getSearchComponentName(this);
+ if (item.providerName == null) {
+ getModelWriter().deleteItemFromDatabase(item);
+ return null;
+ }
+ }
+
if (mIsSafeModeEnabled) {
PendingAppWidgetHostView view =
new PendingAppWidgetHostView(this, item, mIconCache, true);
@@ -2164,7 +2201,8 @@
pendingInfo.spanY = item.spanY;
pendingInfo.minSpanX = item.minSpanX;
pendingInfo.minSpanY = item.minSpanY;
- Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
+ Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(this,
+ pendingInfo);
boolean isDirectConfig =
item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG);
@@ -2257,9 +2295,7 @@
@Override
public void executeOnNextDraw(ViewOnDrawExecutor executor) {
- if (mPendingExecutor != null) {
- mPendingExecutor.markCompleted();
- }
+ clearPendingBinds();
mPendingExecutor = executor;
if (!isInState(ALL_APPS)) {
mAppsView.getAppsStore().enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW);
@@ -2320,6 +2356,11 @@
// override the previous page so we don't log the page switch.
mWorkspace.setCurrentPage(pageBoundFirst, pageBoundFirst /* overridePrevPage */);
+ // Cache one page worth of icons
+ getViewCache().setCacheSize(R.layout.folder_application,
+ mDeviceProfile.inv.numFolderColumns * mDeviceProfile.inv.numFolderRows);
+ getViewCache().setCacheSize(R.layout.folder_page, 2);
+
TraceHelper.endSection("finishBindingItems");
}
@@ -2341,7 +2382,7 @@
*
* Implementation of the method from LauncherModel.Callbacks.
*/
- public void bindAllApplications(ArrayList<AppInfo> apps) {
+ public void bindAllApplications(AppInfo[] apps) {
mAppsView.getAppsStore().setApps(apps);
}
@@ -2354,16 +2395,6 @@
mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
}
- /**
- * A package was updated.
- *
- * Implementation of the method from LauncherModel.Callbacks.
- */
- @Override
- public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps) {
- mAppsView.getAppsStore().addOrUpdateApps(apps);
- }
-
@Override
public void bindPromiseAppProgressUpdated(PromiseAppInfo app) {
mAppsView.getAppsStore().updatePromiseAppProgress(app);
@@ -2411,11 +2442,6 @@
}
@Override
- public void bindAppInfosRemoved(final ArrayList<AppInfo> appInfos) {
- mAppsView.getAppsStore().removeApps(appInfos);
- }
-
- @Override
public void bindAllWidgets(final ArrayList<WidgetListRowEntry> allWidgets) {
mPopupDataProvider.setAllWidgets(allWidgets);
}
@@ -2460,14 +2486,16 @@
}
writer.println(prefix + "Misc:");
- writer.print(prefix + "\tmWorkspaceLoading=" + mWorkspaceLoading);
- writer.print(" mPendingRequestArgs=" + mPendingRequestArgs);
- writer.println(" mPendingActivityResult=" + mPendingActivityResult);
- writer.println(" mRotationHelper: " + mRotationHelper);
+ dumpMisc(prefix + "\t", writer);
+ writer.println(prefix + "\tmWorkspaceLoading=" + mWorkspaceLoading);
+ writer.println(prefix + "\tmPendingRequestArgs=" + mPendingRequestArgs
+ + " mPendingActivityResult=" + mPendingActivityResult);
+ writer.println(prefix + "\tmRotationHelper: " + mRotationHelper);
+ writer.println(prefix + "\tmAppWidgetHost.isListening: " + mAppWidgetHost.isListening());
+
// Extra logging for b/116853349
mDragLayer.dump(prefix, writer);
mStateManager.dump(prefix, writer);
- dumpMisc(writer);
try {
FileLog.flushAll(writer);
@@ -2500,7 +2528,7 @@
KeyEvent.KEYCODE_O, KeyEvent.META_CTRL_ON));
}
if (currentFocus.getTag() instanceof ItemInfo
- && DeepShortcutManager.supportsShortcuts((ItemInfo) currentFocus.getTag())) {
+ && ShortcutUtil.supportsShortcuts((ItemInfo) currentFocus.getTag())) {
shortcutInfos.add(new KeyboardShortcutInfo(
getString(R.string.shortcuts_menu_with_notifications_description),
KeyEvent.KEYCODE_S, KeyEvent.META_CTRL_ON));
@@ -2528,8 +2556,8 @@
if (focusedView instanceof BubbleTextView
&& focusedView.getTag() instanceof ItemInfo
&& mAccessibilityDelegate.performAction(focusedView,
- (ItemInfo) focusedView.getTag(),
- LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) {
+ (ItemInfo) focusedView.getTag(),
+ LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) {
PopupContainerWithArrow.getOpen(this).requestFocus();
return true;
}
@@ -2574,6 +2602,12 @@
return (Launcher) fromContext(context);
}
+ @Override
+ public void returnToHomescreen() {
+ super.returnToHomescreen();
+ getStateManager().goToState(LauncherState.NORMAL);
+ }
+
/**
* Just a wrapper around the type cast to allow easier tracking of calls.
*/
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index d07638a..d70abc2 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -19,14 +19,12 @@
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
-import android.app.KeyguardManager;
import android.content.ComponentName;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
-import android.os.Process;
import android.util.Log;
import com.android.launcher3.compat.LauncherAppsCompat;
@@ -39,6 +37,7 @@
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SecureSettingsObserver;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
public class LauncherAppState {
@@ -46,7 +45,7 @@
// We do not need any synchronization for this variable as its only written on UI thread.
private static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
- new MainThreadInitializedObject<>((c) -> new LauncherAppState(c));
+ new MainThreadInitializedObject<>(LauncherAppState::new);
private final Context mContext;
private final LauncherModel mModel;
@@ -96,6 +95,7 @@
if (FeatureFlags.IS_DOGFOOD_BUILD) {
filter.addAction(ACTION_FORCE_ROLOAD);
}
+ FeatureFlags.APP_SEARCH_IMPROVEMENTS.addChangeListener(context, mModel::forceReload);
mContext.registerReceiver(mModel, filter);
UserManagerCompat.getInstance(mContext).enableAndResetCache();
@@ -150,6 +150,8 @@
LauncherModel setLauncher(Launcher launcher) {
getLocalProvider(mContext).setLauncherProviderChangeListener(launcher);
mModel.initialize(launcher);
+ CustomWidgetManager.INSTANCE.get(launcher)
+ .setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
return mModel;
}
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index 7f5ac52..1215d43 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -27,14 +27,15 @@
import android.content.Intent;
import android.os.Handler;
import android.util.SparseArray;
-import android.view.LayoutInflater;
import android.widget.Toast;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.widget.DeferredAppWidgetHostView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.util.ArrayList;
+import java.util.function.IntConsumer;
/**
@@ -56,9 +57,17 @@
private final Context mContext;
private int mFlags = FLAG_RESUMED;
+ private IntConsumer mAppWidgetRemovedCallback = null;
+
public LauncherAppWidgetHost(Context context) {
+ this(context, null);
+ }
+
+ public LauncherAppWidgetHost(Context context,
+ IntConsumer appWidgetRemovedCallback) {
super(context, APPWIDGET_HOST_ID);
mContext = context;
+ mAppWidgetRemovedCallback = appWidgetRemovedCallback;
}
@Override
@@ -105,6 +114,10 @@
super.stopListening();
}
+ public boolean isListening() {
+ return (mFlags & FLAG_LISTENING) != 0;
+ }
+
/**
* Updates the resumed state of the host.
* When a host is not resumed, it defers calls to startListening until host is resumed again.
@@ -180,10 +193,8 @@
LauncherAppWidgetProviderInfo appWidget) {
if (appWidget.isCustomWidget()) {
LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context);
- LayoutInflater inflater = (LayoutInflater)
- context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(appWidget.initialLayout, lahv);
lahv.setAppWidget(0, appWidget);
+ CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
return lahv;
} else if ((mFlags & FLAG_LISTENING) == 0) {
DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context);
@@ -207,7 +218,7 @@
}
view.setAppWidget(appWidgetId, appWidget);
view.switchToErrorView();
- return view;
+ return view;
}
}
}
@@ -225,6 +236,18 @@
info.initSpans(mContext);
}
+ /**
+ * Called on an appWidget is removed for a widgetId
+ * @param appWidgetId
+ * TODO: make this override when SDK is updated
+ */
+ public void onAppWidgetRemoved(int appWidgetId) {
+ if (mAppWidgetRemovedCallback == null) {
+ return;
+ }
+ mAppWidgetRemovedCallback.accept(appWidgetId);
+ }
+
@Override
public void deleteAppWidgetId(int appWidgetId) {
super.deleteAppWidgetId(appWidgetId);
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index 051846c..b824301 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -29,6 +29,9 @@
*/
public class LauncherAppWidgetInfo extends ItemInfo {
+ public static final int OPTION_SEARCH_WIDGET = 1;
+
+
public static final int RESTORE_COMPLETED = 0;
/**
@@ -97,6 +100,11 @@
public Intent bindOptions;
/**
+ * Widget options
+ */
+ public int options;
+
+ /**
* Nonnull for pending widgets. We use this to get the icon and title for the widget.
*/
public PackageItemInfo pendingItemInfo;
@@ -137,6 +145,7 @@
writer.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId)
.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString())
.put(LauncherSettings.Favorites.RESTORED, restoreStatus)
+ .put(LauncherSettings.Favorites.OPTIONS, options)
.put(LauncherSettings.Favorites.INTENT, bindOptions);
}
@@ -164,4 +173,13 @@
public final boolean hasRestoreFlag(int flag) {
return (restoreStatus & flag) == flag;
}
+
+ /**
+ * returns if widget options include an option or not
+ * @param option
+ * @return
+ */
+ public final boolean hasOptionFlag(int option) {
+ return (options & option) != 0;
+ }
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index d79f5d5..a012412 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -18,28 +18,32 @@
import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.AddWorkspaceItemsTask;
+import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BaseModelUpdateTask;
import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.CacheDataUpdatedTask;
import com.android.launcher3.model.LoaderResults;
import com.android.launcher3.model.LoaderTask;
@@ -49,28 +53,22 @@
import com.android.launcher3.model.ShortcutsChangedTask;
import com.android.launcher3.model.UserLockStateChangedTask;
import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Thunk;
-import com.android.launcher3.util.ViewOnDrawExecutor;
-import com.android.launcher3.widget.WidgetListRowEntry;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
-import androidx.annotation.Nullable;
-
/**
* Maintains in-memory state of the Launcher. It is expected that there should be only one
* LauncherModel object held in a static. Also provide APIs for updating the database state
@@ -82,21 +80,12 @@
static final String TAG = "Launcher.Model";
- private final MainThreadExecutor mUiExecutor = new MainThreadExecutor();
@Thunk final LauncherAppState mApp;
@Thunk final Object mLock = new Object();
@Thunk
LoaderTask mLoaderTask;
@Thunk boolean mIsLoaderTaskRunning;
- @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
- private static final Looper mWorkerLooper;
- static {
- sWorkerThread.start();
- mWorkerLooper = sWorkerThread.getLooper();
- }
- @Thunk static final Handler sWorker = new Handler(mWorkerLooper);
-
// Indicates whether the current model data is valid or not.
// We start off with everything not loaded. After that, we assume that
// our monitoring of the package manager provides all updates and we never
@@ -133,33 +122,6 @@
}
};
- public interface Callbacks {
- public void rebindModel();
-
- public int getCurrentWorkspaceScreen();
- public void clearPendingBinds();
- public void startBinding();
- public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
- public void bindScreens(IntArray orderedScreenIds);
- public void finishFirstPageBind(ViewOnDrawExecutor executor);
- public void finishBindingItems(int pageBoundFirst);
- public void bindAllApplications(ArrayList<AppInfo> apps);
- public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps);
- public void preAddApps();
- public void bindAppsAdded(IntArray newScreens,
- ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated);
- public void bindPromiseAppProgressUpdated(PromiseAppInfo app);
- public void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated);
- public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
- public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
- public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
- public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
- public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets);
- public void onPageBoundSynchronously(int page);
- public void executeOnNextDraw(ViewOnDrawExecutor executor);
- public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
- }
-
LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
mApp = app;
mBgAllAppsList = new AllAppsList(iconCache, appFilter);
@@ -172,11 +134,11 @@
/**
* Updates the icons and label of all pending icons for the provided package name.
*/
- public void updateSessionDisplayInfo(final String packageName) {
+ public void updateSessionDisplayInfo(final String packageName, final UserHandle user) {
HashSet<String> packages = new HashSet<>();
packages.add(packageName);
enqueueModelUpdateTask(new CacheDataUpdatedTask(
- CacheDataUpdatedTask.OP_SESSION_UPDATE, Process.myUserHandle(), packages));
+ CacheDataUpdatedTask.OP_SESSION_UPDATE, user, packages));
}
/**
@@ -211,6 +173,30 @@
enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
}
+ public void onSessionFailure(String packageName, UserHandle user) {
+ enqueueModelUpdateTask(new BaseModelUpdateTask() {
+ @Override
+ public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+ final IntSparseArrayMap<Boolean> removedIds = new IntSparseArrayMap<>();
+ synchronized (dataModel) {
+ for (ItemInfo info : dataModel.itemsIdMap) {
+ if (info instanceof WorkspaceItemInfo
+ && ((WorkspaceItemInfo) info).hasPromiseIconUi()
+ && user.equals(info.user)
+ && info.getIntent() != null
+ && TextUtils.equals(packageName, info.getIntent().getPackage())) {
+ removedIds.put(info.id, true /* remove */);
+ }
+ }
+ }
+
+ if (!removedIds.isEmpty()) {
+ deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedIds, false));
+ }
+ }
+ });
+ }
+
@Override
public void onPackageRemoved(String packageName, UserHandle user) {
onPackagesRemoved(user, packageName);
@@ -218,6 +204,7 @@
public void onPackagesRemoved(UserHandle user, String... packages) {
int op = PackageUpdatedTask.OP_REMOVE;
+ FileLog.d(TAG, "package removed received " + String.join("," + packages));
enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));
}
@@ -273,7 +260,6 @@
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
-
final String action = intent.getAction();
if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
// If we have changed locale we need to clear out the labels in all apps/workspace.
@@ -349,7 +335,7 @@
if (mCallbacks != null && mCallbacks.get() != null) {
final Callbacks oldCallbacks = mCallbacks.get();
// Clear any pending bind-runnables from the synchronized load process.
- mUiExecutor.execute(oldCallbacks::clearPendingBinds);
+ MAIN_EXECUTOR.execute(oldCallbacks::clearPendingBinds);
// If there is already one running, tell it to stop.
stopLoader();
@@ -393,7 +379,7 @@
// Always post the loader task, instead of running directly (even on same thread) so
// that we exit any nested synchronized blocks
- sWorker.post(mLoaderTask);
+ MODEL_EXECUTOR.post(mLoaderTask);
}
}
@@ -411,16 +397,7 @@
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
apps.addPromiseApp(app.getContext(), sessionInfo);
- if (!apps.added.isEmpty()) {
- final ArrayList<AppInfo> arrayList = new ArrayList<>(apps.added);
- apps.added.clear();
- scheduleCallbackTask(new CallbackTask() {
- @Override
- public void execute(Callbacks callbacks) {
- callbacks.bindAppsAddedOrUpdated(arrayList);
- }
- });
- }
+ bindApplicationsIfNeeded();
}
});
}
@@ -469,8 +446,8 @@
* use partial updates similar to {@link UserManagerCompat}
*/
public void refreshShortcutsIfRequired() {
- sWorker.removeCallbacks(mShortcutPermissionCheckRunnable);
- sWorker.post(mShortcutPermissionCheckRunnable);
+ MODEL_EXECUTOR.getHandler().removeCallbacks(mShortcutPermissionCheckRunnable);
+ MODEL_EXECUTOR.post(mShortcutPermissionCheckRunnable);
}
/**
@@ -497,14 +474,8 @@
}
public void enqueueModelUpdateTask(ModelUpdateTask task) {
- task.init(mApp, this, sBgDataModel, mBgAllAppsList, mUiExecutor);
-
- if (sWorkerThread.getThreadId() == Process.myTid()) {
- task.run();
- } else {
- // If we are not on the worker thread, then post to the worker handler
- sWorker.post(task);
- }
+ task.init(mApp, this, sBgDataModel, mBgAllAppsList, MAIN_EXECUTOR);
+ MODEL_EXECUTOR.execute(task);
}
/**
@@ -579,15 +550,4 @@
public Callbacks getCallback() {
return mCallbacks != null ? mCallbacks.get() : null;
}
-
- /**
- * @return the looper for the worker thread which can be used to start background tasks.
- */
- public static Looper getWorkerLooper() {
- return mWorkerLooper;
- }
-
- public static void setWorkerPriority(final int priority) {
- Process.setThreadPriority(sWorkerThread.getThreadId(), priority);
- }
}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 6ad5c36..6081300 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -33,7 +33,6 @@
import android.content.Intent;
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ProviderInfo;
import android.content.res.Resources;
import android.database.Cursor;
@@ -50,7 +49,6 @@
import android.os.Message;
import android.os.Process;
import android.os.UserHandle;
-import android.os.UserManager;
import android.provider.BaseColumns;
import android.provider.Settings;
import android.text.TextUtils;
@@ -70,6 +68,7 @@
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.NoLocaleSQLiteHelper;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Thunk;
@@ -77,7 +76,6 @@
import java.io.File;
import java.io.FileDescriptor;
-import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringReader;
@@ -873,7 +871,7 @@
continue;
}
- if (!Utilities.isLauncherAppTarget(intent)) {
+ if (!PackageManagerHelper.isLauncherAppTarget(intent)) {
continue;
}
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index e248ba0..c509680 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -59,6 +59,10 @@
public static final String ITEM_TYPE = "itemType";
/**
+ * The gesture is a package
+ */
+ public static final int ITEM_TYPE_NON_ACTIONABLE = -1;
+ /**
* The gesture is an application
*/
public static final int ITEM_TYPE_APPLICATION = 0;
@@ -122,11 +126,13 @@
*/
public static final int CONTAINER_DESKTOP = -100;
public static final int CONTAINER_HOTSEAT = -101;
+ public static final int CONTAINER_PREDICTION = -102;
static final String containerToString(int container) {
switch (container) {
case CONTAINER_DESKTOP: return "desktop";
case CONTAINER_HOTSEAT: return "hotseat";
+ case CONTAINER_PREDICTION: return "prediction";
default: return String.valueOf(container);
}
}
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index dcfd272..6e2626b 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -243,6 +243,10 @@
return 0;
}
+ public float getOverviewScrimAlpha(Launcher launcher) {
+ return 0;
+ }
+
public String getDescription(Launcher launcher) {
return launcher.getWorkspace().getCurrentPageDescription();
}
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 2c8c208..f673508 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -24,7 +24,8 @@
import android.animation.AnimatorSet;
import android.os.Handler;
import android.os.Looper;
-import android.util.Log;
+
+import androidx.annotation.IntDef;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -32,7 +33,6 @@
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.anim.PropertySetter.AnimatedPropertySetter;
import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.uioverrides.UiFactory;
import java.io.PrintWriter;
@@ -40,8 +40,6 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import androidx.annotation.IntDef;
-
/**
* TODO: figure out what kind of tests we can write for this
*
@@ -136,7 +134,7 @@
}
public void dump(String prefix, PrintWriter writer) {
- writer.println(prefix + "LauncherState");
+ writer.println(prefix + "LauncherState:");
writer.println(prefix + "\tmLastStableState:" + mLastStableState);
writer.println(prefix + "\tmCurrentStableState:" + mCurrentStableState);
writer.println(prefix + "\tmState:" + mState);
@@ -403,16 +401,12 @@
}
private void onStateTransitionStart(LauncherState state) {
- if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_DRAG_TAG,
- "onStateTransitionStart");
- }
if (mState != state) {
mState.onStateDisabled(mLauncher);
}
mState = state;
mState.onStateEnabled(mLauncher);
- mLauncher.onStateSet(mState);
+ mLauncher.onStateSetStart(mState);
if (state.disablePageClipping) {
// Only disable clipping if needed, otherwise leave it as previous value.
@@ -429,17 +423,11 @@
// Only change the stable states after the transitions have finished
if (state != mCurrentStableState) {
mLastStableState = state.getHistoryForState(mCurrentStableState);
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG,
- "mCurrentStableState = " + state.getClass().getSimpleName() + " @ " +
- android.util.Log.getStackTraceString(new Throwable()));
- }
mCurrentStableState = state;
}
state.onStateTransitionEnd(mLauncher);
- mLauncher.getWorkspace().setClipChildren(!state.disablePageClipping);
- mLauncher.finishAutoCancelActionMode();
+ mLauncher.onStateSetEnd(state);
if (state == NORMAL) {
setRestState(null);
@@ -580,10 +568,6 @@
private final AnimatorSet mAnim;
public StartAnimRunnable(AnimatorSet anim) {
- if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_DRAG_TAG,
- "StartAnimRunnable");
- }
mAnim = anim;
}
diff --git a/src/com/android/launcher3/MainThreadExecutor.java b/src/com/android/launcher3/MainThreadExecutor.java
deleted file mode 100644
index 5094682..0000000
--- a/src/com/android/launcher3/MainThreadExecutor.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2014 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 android.os.Looper;
-
-import com.android.launcher3.util.LooperExecutor;
-
-/**
- * An executor service that executes its tasks on the main thread.
- *
- * Shutting down this executor is not supported.
- */
-public class MainThreadExecutor extends LooperExecutor {
-
- public MainThreadExecutor() {
- super(Looper.getMainLooper());
- }
-}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 2eeb132..ff2b400 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -16,7 +16,6 @@
package com.android.launcher3;
-import static com.android.launcher3.Utilities.shouldDisableGestures;
import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
@@ -48,6 +47,7 @@
import android.widget.ScrollView;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.touch.OverScroll;
@@ -368,6 +368,7 @@
*/
protected void onPageEndTransition() {
mWasInOverscroll = false;
+ AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
}
protected int getUnboundedScrollX() {
@@ -847,7 +848,7 @@
*/
// Skip touch handling if there are no pages to swipe
- if (getChildCount() <= 0 || shouldDisableGestures(ev)) return false;
+ if (getChildCount() <= 0) return false;
acquireVelocityTrackerAndAddMovement(ev);
@@ -889,23 +890,7 @@
mTotalMotionX = 0;
mActivePointerId = ev.getPointerId(0);
- /*
- * If being flinged and user touches the screen, initiate drag;
- * otherwise don't. mScroller.isFinished should be false when
- * being flinged.
- */
- final int xDist = Math.abs(mScroller.getFinalPos() - mScroller.getCurrPos());
- final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3);
-
- if (finishedScrolling) {
- mIsBeingDragged = false;
- if (!mScroller.isFinished() && !mFreeScroll) {
- setCurrentPage(getNextPage());
- pageEndTransition();
- }
- } else {
- mIsBeingDragged = true;
- }
+ updateIsBeingDraggedOnTouchDown();
break;
}
@@ -928,6 +913,25 @@
return mIsBeingDragged;
}
+ /**
+ * If being flinged and user touches the screen, initiate drag; otherwise don't.
+ */
+ private void updateIsBeingDraggedOnTouchDown() {
+ // mScroller.isFinished should be false when being flinged.
+ final int xDist = Math.abs(mScroller.getFinalPos() - mScroller.getCurrPos());
+ final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3);
+
+ if (finishedScrolling) {
+ mIsBeingDragged = false;
+ if (!mScroller.isFinished() && !mFreeScroll) {
+ setCurrentPage(getNextPage());
+ pageEndTransition();
+ }
+ } else {
+ mIsBeingDragged = true;
+ }
+ }
+
public boolean isHandlingTouch() {
return mIsBeingDragged;
}
@@ -1084,7 +1088,9 @@
if (mFreeScroll) {
setCurrentPage(getNextPage());
} else if (wasFreeScroll) {
- snapToPage(getNextPage());
+ if (getScrollForPage(getNextPage()) != getScrollX()) {
+ snapToPage(getNextPage());
+ }
}
}
@@ -1095,7 +1101,7 @@
@Override
public boolean onTouchEvent(MotionEvent ev) {
// Skip touch handling if there are no pages to swipe
- if (getChildCount() <= 0 || shouldDisableGestures(ev)) return false;
+ if (getChildCount() <= 0) return false;
acquireVelocityTrackerAndAddMovement(ev);
@@ -1103,6 +1109,8 @@
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
+ updateIsBeingDraggedOnTouchDown();
+
/*
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
@@ -1288,6 +1296,10 @@
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
switch (event.getAction()) {
case MotionEvent.ACTION_SCROLL: {
+ Launcher launcher = Launcher.getLauncher(getContext());
+ if (launcher != null) {
+ AbstractFloatingView.closeAllOpenViews(launcher);
+ }
// Handle mouse (or ext. device) by shifting the page depending on the scroll
final float vscroll;
final float hscroll;
@@ -1298,6 +1310,9 @@
vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
}
+ if (Math.abs(vscroll) > Math.abs(hscroll) && !isVerticalScrollable()) {
+ return true;
+ }
if (hscroll != 0 || vscroll != 0) {
boolean isForwardScroll = mIsRtl ? (hscroll < 0 || vscroll < 0)
: (hscroll > 0 || vscroll > 0);
@@ -1314,6 +1329,10 @@
return super.onGenericMotionEvent(event);
}
+ protected boolean isVerticalScrollable() {
+ return true;
+ }
+
private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
@@ -1531,7 +1550,7 @@
snapToPage(getNextPage() - 1);
return true;
}
- return false;
+ return onOverscroll(-getMeasuredWidth());
}
public boolean scrollRight() {
@@ -1539,7 +1558,15 @@
snapToPage(getNextPage() + 1);
return true;
}
- return false;
+ return onOverscroll(getMeasuredWidth());
+ }
+
+ protected boolean onOverscroll(int amount) {
+ if (!mAllowOverScroll) return false;
+ onScrollInteractionBegin();
+ overScroll(amount);
+ onScrollInteractionEnd();
+ return true;
}
@Override
@@ -1559,16 +1586,24 @@
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
final boolean pagesFlipped = isPageOrderFlipped();
- info.setScrollable(getPageCount() > 1);
- if (getCurrentPage() < getPageCount() - 1) {
- info.addAction(pagesFlipped ? AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD
- : AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ int offset = (mAllowOverScroll ? 0 : 1);
+ info.setScrollable(getPageCount() > offset);
+ if (getCurrentPage() < getPageCount() - offset) {
+ info.addAction(pagesFlipped ?
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD
+ : AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
+ info.addAction(mIsRtl ?
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT
+ : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT);
}
- if (getCurrentPage() > 0) {
- info.addAction(pagesFlipped ? AccessibilityNodeInfo.ACTION_SCROLL_FORWARD
- : AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ if (getCurrentPage() >= offset) {
+ info.addAction(pagesFlipped ?
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD
+ : AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
+ info.addAction(mIsRtl ?
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT
+ : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT);
}
-
// Accessibility-wise, PagedView doesn't support long click, so disabling it.
// Besides disabling the accessibility long-click, this also prevents this view from getting
// accessibility focus.
@@ -1587,7 +1622,7 @@
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
- event.setScrollable(getPageCount() > 1);
+ event.setScrollable(mAllowOverScroll || getPageCount() > 1);
}
@Override
@@ -1606,8 +1641,21 @@
if (pagesFlipped ? scrollRight() : scrollLeft()) {
return true;
}
+ } break;
+ case android.R.id.accessibilityActionPageRight: {
+ if (!mIsRtl) {
+ return scrollRight();
+ } else {
+ return scrollLeft();
+ }
}
- break;
+ case android.R.id.accessibilityActionPageLeft: {
+ if (!mIsRtl) {
+ return scrollLeft();
+ } else {
+ return scrollRight();
+ }
+ }
}
return false;
}
diff --git a/src/com/android/launcher3/Partner.java b/src/com/android/launcher3/Partner.java
index 380078b..af5402a 100644
--- a/src/com/android/launcher3/Partner.java
+++ b/src/com/android/launcher3/Partner.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static com.android.launcher3.util.PackageManagerHelper.findSystemApk;
+
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.util.DisplayMetrics;
@@ -59,7 +61,7 @@
*/
public static synchronized Partner get(PackageManager pm) {
if (!sSearched) {
- Pair<String, Resources> apkInfo = Utilities.findSystemApk(ACTION_PARTNER_CUSTOMIZATION, pm);
+ Pair<String, Resources> apkInfo = findSystemApk(ACTION_PARTNER_CUSTOMIZATION, pm);
if (apkInfo != null) {
sPartner = new Partner(apkInfo.first, apkInfo.second);
}
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 55cb6f2..c8c590d 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -2,6 +2,7 @@
import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE;
+
import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_MASK;
import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_NO;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
@@ -29,6 +30,7 @@
import com.android.launcher3.Launcher.OnResumeCallback;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.logging.FileLog;
import com.android.launcher3.logging.LoggerUtils;
import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -240,6 +242,7 @@
.setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
.putExtra(Intent.EXTRA_USER, info.user);
mLauncher.startActivity(i);
+ FileLog.d(TAG, "start uninstall activity " + cn.getPackageName());
return cn;
} catch (URISyntaxException e) {
Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info);
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index b0da6b9..a87c446 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -18,6 +18,7 @@
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -27,19 +28,23 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
+import android.graphics.Bitmap;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
-import android.os.Process;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.compat.PackageInstallerCompat;
import java.util.List;
+import static com.android.launcher3.compat.PackageInstallerCompat.getUserHandle;
+
/**
* BroadcastReceiver to handle session commit intent.
*/
@@ -66,15 +71,34 @@
SessionInfo info = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
+ if (!PackageInstaller.ACTION_SESSION_COMMITTED.equals(intent.getAction())
+ || info == null || user == null) {
+ // Invalid intent.
+ return;
+ }
- if (TextUtils.isEmpty(info.getAppPackageName()) ||
- info.getInstallReason() != PackageManager.INSTALL_REASON_USER) {
+ PackageInstallerCompat packageInstallerCompat = PackageInstallerCompat.getInstance(context);
+ if (TextUtils.isEmpty(info.getAppPackageName())
+ || info.getInstallReason() != PackageManager.INSTALL_REASON_USER
+ || packageInstallerCompat.promiseIconAddedForId(info.getSessionId())) {
+ packageInstallerCompat.removePromiseIconId(info.getSessionId());
return;
}
queueAppIconAddition(context, info.getAppPackageName(), user);
}
+ public static void queuePromiseAppIconAddition(Context context, SessionInfo sessionInfo) {
+ String packageName = sessionInfo.getAppPackageName();
+ List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(context)
+ .getActivityList(packageName, getUserHandle(sessionInfo));
+ if (activities == null || activities.isEmpty()) {
+ // Ensure application isn't already installed.
+ queueAppIconAddition(context, packageName, sessionInfo.getAppLabel(),
+ sessionInfo.getAppIcon(), getUserHandle(sessionInfo));
+ }
+ }
+
public static void queueAppIconAddition(Context context, String packageName, UserHandle user) {
List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(context)
.getActivityList(packageName, user);
@@ -82,7 +106,18 @@
// no activity found
return;
}
- InstallShortcutReceiver.queueActivityInfo(activities.get(0), context);
+ queueAppIconAddition(context, packageName, activities.get(0).getLabel(), null, user);
+ }
+
+ private static void queueAppIconAddition(Context context, String packageName,
+ CharSequence label, Bitmap icon, UserHandle user) {
+ Intent data = new Intent();
+ data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent().setComponent(
+ new ComponentName(packageName, "")).setPackage(packageName));
+ data.putExtra(Intent.EXTRA_SHORTCUT_NAME, label);
+ data.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
+
+ InstallShortcutReceiver.queueApplication(data, user, context);
}
public static boolean isEnabled(Context context) {
@@ -103,7 +138,7 @@
// grid.
prefs.edit().putBoolean(ADD_ICON_PREFERENCE_KEY, true).apply();
} else if (!prefs.contains(ADD_ICON_PREFERENCE_INITIALIZED_KEY)) {
- new PrefInitTask(context).executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
+ new PrefInitTask(context).executeOnExecutor(Executors.THREAD_POOL_EXECUTOR);
}
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 65aa3a7..5d0effa 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -16,20 +16,17 @@
package com.android.launcher3;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
+
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityManager;
+import android.app.Person;
import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
@@ -44,7 +41,6 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.os.Build;
-import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.Message;
@@ -57,7 +53,6 @@
import android.text.style.TtsSpan;
import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.Pair;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
@@ -66,7 +61,6 @@
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.ShortcutConfigActivityInfo;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.graphics.TintedDrawableSpan;
@@ -78,22 +72,13 @@
import com.android.launcher3.views.Transposable;
import com.android.launcher3.widget.PendingAddShortcutInfo;
-import java.io.Closeable;
-import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
-import java.util.StringTokenizer;
-import java.util.concurrent.Executor;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
-
/**
* Various utilities shared amongst the Launcher's classes.
*/
@@ -109,6 +94,9 @@
private static final Matrix sMatrix = new Matrix();
private static final Matrix sInverseMatrix = new Matrix();
+ public static final String[] EMPTY_STRING_ARRAY = new String[0];
+ public static final Person[] EMPTY_PERSON_ARRAY = new Person[0];
+
public static final boolean ATLEAST_Q = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
public static final boolean ATLEAST_P =
@@ -120,24 +108,12 @@
public static final boolean ATLEAST_OREO =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
- public static final int SINGLE_FRAME_MS = 16;
-
/**
* Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}.
*/
public static final int EDGE_NAV_BAR = 1 << 8;
/**
- * Set on a motion event do disallow any gestures and only handle touch.
- * See {@link MotionEvent#setEdgeFlags(int)}.
- */
- public static final int FLAG_NO_GESTURES = 1 << 9;
-
- public static boolean shouldDisableGestures(MotionEvent ev) {
- return (ev.getEdgeFlags() & FLAG_NO_GESTURES) == FLAG_NO_GESTURES;
- }
-
- /**
* Indicates if the device has a debug build. Should only be used to store additional info or
* add extra logging and not for changing the app behavior.
*/
@@ -154,18 +130,6 @@
public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR";
- // These values are same as that in {@link AsyncTask}.
- private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
- private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
- private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
- private static final int KEEP_ALIVE = 1;
- /**
- * An {@link Executor} to be used with async task with no limit on the queue size.
- */
- public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
- CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
- TimeUnit.SECONDS, new LinkedBlockingQueue<>());
-
public static boolean IS_RUNNING_IN_TEST_HARNESS =
ActivityManager.isRunningInTestHarness();
@@ -253,7 +217,6 @@
return scale;
}
-
/**
* Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, float[], boolean)}.
*/
@@ -389,53 +352,6 @@
return min + (value * (max - min));
}
- public static boolean isSystemApp(Context context, Intent intent) {
- PackageManager pm = context.getPackageManager();
- ComponentName cn = intent.getComponent();
- String packageName = null;
- if (cn == null) {
- ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
- if ((info != null) && (info.activityInfo != null)) {
- packageName = info.activityInfo.packageName;
- }
- } else {
- packageName = cn.getPackageName();
- }
- if (packageName != null) {
- try {
- PackageInfo info = pm.getPackageInfo(packageName, 0);
- return (info != null) && (info.applicationInfo != null) &&
- ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
- } catch (NameNotFoundException e) {
- return false;
- }
- } else {
- return false;
- }
- }
-
- /*
- * Finds a system apk which had a broadcast receiver listening to a particular action.
- * @param action intent action used to find the apk
- * @return a pair of apk package name and the resources.
- */
- static Pair<String, Resources> findSystemApk(String action, PackageManager pm) {
- final Intent intent = new Intent(action);
- for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) {
- if (info.activityInfo != null &&
- (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- final String packageName = info.activityInfo.packageName;
- try {
- final Resources res = pm.getResourcesForApplication(packageName);
- return Pair.create(packageName, res);
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Failed to find resources for " + packageName);
- }
- }
- }
- return null;
- }
-
/**
* Trims the string, removing all whitespace at the beginning and end of the string.
* Non-breaking whitespaces are also removed.
@@ -460,51 +376,10 @@
return (int) Math.ceil(fm.bottom - fm.top);
}
- /**
- * Convenience println with multiple args.
- */
- public static void println(String key, Object... args) {
- StringBuilder b = new StringBuilder();
- b.append(key);
- b.append(": ");
- boolean isFirstArgument = true;
- for (Object arg : args) {
- if (isFirstArgument) {
- isFirstArgument = false;
- } else {
- b.append(", ");
- }
- b.append(arg);
- }
- System.out.println(b.toString());
- }
-
public static boolean isRtl(Resources res) {
return res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
}
- /**
- * Returns true if the intent is a valid launch intent for a launcher activity of an app.
- * This is used to identify shortcuts which are different from the ones exposed by the
- * applications' manifest file.
- *
- * @param launchIntent The intent that will be launched when the shortcut is clicked.
- */
- public static boolean isLauncherAppTarget(Intent launchIntent) {
- if (launchIntent != null
- && Intent.ACTION_MAIN.equals(launchIntent.getAction())
- && launchIntent.getComponent() != null
- && launchIntent.getCategories() != null
- && launchIntent.getCategories().size() == 1
- && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
- && TextUtils.isEmpty(launchIntent.getDataString())) {
- // An app target can either have no extra or have ItemInfo.EXTRA_PROFILE.
- Bundle extras = launchIntent.getExtras();
- return extras == null || extras.keySet().isEmpty();
- }
- return false;
- }
-
public static float dpiFromPx(int size, DisplayMetrics metrics){
float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
return (size / densityRatio);
@@ -607,18 +482,6 @@
return context.getSystemService(WallpaperManager.class).isSetWallpaperAllowed();
}
- public static void closeSilently(Closeable c) {
- if (c != null) {
- try {
- c.close();
- } catch (IOException e) {
- if (FeatureFlags.IS_DOGFOOD_BUILD) {
- Log.d(TAG, "Error closing", e);
- }
- }
- }
- }
-
public static boolean isBinderSizeError(Exception e) {
return e.getCause() instanceof TransactionTooLargeException
|| e.getCause() instanceof DeadObjectException;
@@ -721,7 +584,7 @@
LauncherIcons li = LauncherIcons.obtain(appState.getContext());
Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).iconBitmap;
li.recycle();
- float badgeSize = launcher.getResources().getDimension(R.dimen.profile_badge_size);
+ float badgeSize = LauncherIcons.getBadgeSizeForIconSize(iconSize);
float insetFraction = (iconSize - badgeSize) / iconSize;
return new InsetDrawable(new FastBitmapDrawable(badge),
insetFraction, insetFraction, 0, 0);
@@ -733,25 +596,6 @@
}
}
- public static int[] getIntArrayFromString(String tokenized) {
- StringTokenizer tokenizer = new StringTokenizer(tokenized, ",");
- int[] array = new int[tokenizer.countTokens()];
- int count = 0;
- while (tokenizer.hasMoreTokens()) {
- array[count] = Integer.parseInt(tokenizer.nextToken());
- count++;
- }
- return array;
- }
-
- public static String getStringFromIntArray(int[] array) {
- StringBuilder str = new StringBuilder();
- for (int value : array) {
- str.append(value).append(",");
- }
- return str.toString();
- }
-
public static float squaredHypot(float x, float y) {
return x * x + y * y;
}
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 6d1bc1a..c6381b0 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -1,5 +1,8 @@
package com.android.launcher3;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
@@ -23,21 +26,23 @@
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.CancellationSignal;
-import android.os.Handler;
import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
import android.util.LongSparseArray;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.ShortcutConfigActivityInfo;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.icons.GraphicsUtils;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.ShadowGenerator;
-import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Executors;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SQLiteCacheHelper;
@@ -50,11 +55,8 @@
import java.util.HashSet;
import java.util.Set;
import java.util.WeakHashMap;
-import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
-import androidx.annotation.Nullable;
-
public class WidgetPreviewLoader {
private static final String TAG = "WidgetPreviewLoader";
@@ -68,23 +70,18 @@
* Note: synchronized block used for this variable is expensive and the block should always
* be posted to a background thread.
*/
- @Thunk final Set<Bitmap> mUnusedBitmaps =
- Collections.newSetFromMap(new WeakHashMap<Bitmap, Boolean>());
+ @Thunk final Set<Bitmap> mUnusedBitmaps = Collections.newSetFromMap(new WeakHashMap<>());
private final Context mContext;
private final IconCache mIconCache;
private final UserManagerCompat mUserManager;
private final CacheDb mDb;
- private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
- @Thunk final Handler mWorkerHandler;
-
public WidgetPreviewLoader(Context context, IconCache iconCache) {
mContext = context;
mIconCache = iconCache;
mUserManager = UserManagerCompat.getInstance(context);
mDb = new CacheDb(context);
- mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
}
/**
@@ -99,7 +96,7 @@
WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size);
PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller);
- task.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
+ task.executeOnExecutor(Executors.THREAD_POOL_EXECUTOR);
CancellationSignal signal = new CancellationSignal();
signal.setOnCancelListener(task);
@@ -453,7 +450,7 @@
private Bitmap generateShortcutPreview(BaseActivity launcher, ShortcutConfigActivityInfo info,
int maxWidth, int maxHeight, Bitmap preview) {
- int iconSize = launcher.getDeviceProfile().iconSizePx;
+ int iconSize = launcher.getDeviceProfile().allAppsIconSizePx;
int padding = launcher.getResources()
.getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
@@ -494,12 +491,7 @@
private Drawable mutateOnMainThread(final Drawable drawable) {
try {
- return mMainThreadExecutor.submit(new Callable<Drawable>() {
- @Override
- public Drawable call() throws Exception {
- return drawable.mutate();
- }
- }).get();
+ return MAIN_EXECUTOR.submit(drawable::mutate).get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
@@ -607,7 +599,7 @@
// Write the generated preview to the DB in the worker thread
if (mVersions != null) {
- mWorkerHandler.post(new Runnable() {
+ MODEL_EXECUTOR.post(new Runnable() {
@Override
public void run() {
if (!isCancelled()) {
@@ -637,7 +629,7 @@
// recycled set immediately. Otherwise, it will be recycled after the preview is written
// to disk.
if (preview != null) {
- mWorkerHandler.post(new Runnable() {
+ MODEL_EXECUTOR.post(new Runnable() {
@Override
public void run() {
synchronized (mUnusedBitmaps) {
@@ -658,7 +650,7 @@
// in the tasks's onCancelled() call, and if cancelled while the task is writing to
// disk, it will be cancelled in the task's onPostExecute() call.
if (mBitmapToRecycle != null) {
- mWorkerHandler.post(new Runnable() {
+ MODEL_EXECUTOR.post(new Runnable() {
@Override
public void run() {
synchronized (mUnusedBitmaps) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 9f846bb..9eeb286 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -67,9 +67,9 @@
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.dot.FolderDotInfo;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dot.FolderDotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
@@ -81,6 +81,7 @@
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.pageindicators.WorkspacePageIndicator;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
@@ -89,8 +90,8 @@
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.Executors;
import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.PackageUserKey;
@@ -132,9 +133,6 @@
private static final int DEFAULT_PAGE = 0;
- public static final boolean MAP_NO_RECURSE = false;
- public static final boolean MAP_RECURSE = true;
-
private LayoutTransition mLayoutTransition;
@Thunk final WallpaperManager mWallpaperManager;
@@ -370,10 +368,6 @@
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
- if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_DRAG_TAG,
- "onDragStart 1");
- }
if (ENFORCE_DRAG_EVENT_ORDER) {
enforceDragParity("onDragStart", 0, 0);
}
@@ -425,8 +419,7 @@
// Always enter the spring loaded mode
if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_DRAG_TAG,
- "onDragStart 2");
+ Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "Switching to SPRING_LOADED");
}
mLauncher.getStateManager().goToState(SPRING_LOADED);
}
@@ -1042,6 +1035,13 @@
}
@Override
+ protected boolean onOverscroll(int amount) {
+ // Enforce overscroll on -1 direction
+ if ((amount > 0 && !mIsRtl) || (amount < 0 && mIsRtl)) return false;
+ return super.onOverscroll(amount);
+ }
+
+ @Override
protected boolean shouldFlingForVelocity(int velocityX) {
// When the overlay is moving, the fling or settle transition is controlled by the overlay.
return Float.compare(Math.abs(mOverlayTranslation), 0) == 0 &&
@@ -1056,13 +1056,18 @@
if (!mOverlayShown) {
mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
Action.Direction.LEFT, ContainerType.WORKSPACE, 0);
+ mLauncher.getStatsLogManager().logSwipeOnContainer(true, 0);
}
mOverlayShown = true;
// Not announcing the overlay page for accessibility since it announces itself.
} else if (Float.compare(scroll, 0f) == 0) {
if (mOverlayShown) {
- mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
+ UserEventDispatcher ued = mLauncher.getUserEventDispatcher();
+ if (!ued.isPreviousHomeGesture()) {
+ mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
Action.Direction.RIGHT, ContainerType.WORKSPACE, -1);
+ mLauncher.getStatsLogManager().logSwipeOnContainer(false, -1);
+ }
} else if (Float.compare(mOverlayTranslation, 0f) != 0) {
// When arriving to 0 overscroll from non-zero overscroll, announce page for
// accessibility since default announcements were disabled while in overscroll
@@ -1160,7 +1165,7 @@
}
protected void setWallpaperDimension() {
- Utilities.THREAD_POOL_EXECUTOR.execute(new Runnable() {
+ Executors.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
final Point size = LauncherAppState.getIDP(getContext()).defaultWallpaperSize;
@@ -1270,6 +1275,10 @@
return !mLauncher.isInState(NORMAL);
}
+ private boolean workspaceInScrollableState() {
+ return mLauncher.isInState(SPRING_LOADED) || !workspaceInModalState();
+ }
+
/** Returns whether a drag should be allowed to be started from the current workspace state. */
public boolean workspaceIconsCanBeDragged() {
return mLauncher.getStateManager().getState().workspaceIconsCanBeDragged;
@@ -1463,6 +1472,9 @@
public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
DragPreviewProvider previewProvider, DragOptions dragOptions) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_CONTEXT_MENU, "beginDragShared");
+ }
float iconScale = 1f;
if (child instanceof BubbleTextView) {
Drawable icon = ((BubbleTextView) child).getIcon();
@@ -1488,7 +1500,7 @@
Rect dragRect = null;
if (child instanceof BubbleTextView) {
dragRect = new Rect();
- ((BubbleTextView) child).getIconBounds(dragRect);
+ BubbleTextView.getIconBounds(child, dragRect, grid.iconSizePx);
dragLayerY += dragRect.top;
// Note: The dragRect is used to calculate drag layer offsets, but the
// dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
@@ -1746,6 +1758,9 @@
public void prepareAccessibilityDrop() { }
public void onDrop(final DragObject d, DragOptions options) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "Workspace.onDrop");
+ }
mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
CellLayout dropTargetLayout = mDropToLayout;
@@ -2423,6 +2438,9 @@
* to add an item to one of the workspace screens.
*/
private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "Workspace.onDropExternal");
+ }
if (d.dragInfo instanceof PendingAddShortcutInfo) {
WorkspaceItemInfo si = ((PendingAddShortcutInfo) d.dragInfo)
.activityInfo.createWorkspaceItemInfo();
@@ -2525,22 +2543,22 @@
View view;
switch (info.itemType) {
- case ITEM_TYPE_APPLICATION:
- case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
- case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
- if (info.container == NO_ID && info instanceof AppInfo) {
- // Came from all apps -- make a copy
- info = ((AppInfo) info).makeWorkspaceItem();
- d.dragInfo = info;
- }
- view = mLauncher.createShortcut(cellLayout, (WorkspaceItemInfo) info);
- break;
- case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
- (FolderInfo) info);
- break;
- default:
- throw new IllegalStateException("Unknown item type: " + info.itemType);
+ case ITEM_TYPE_APPLICATION:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+ if (info instanceof AppInfo) {
+ // Came from all apps -- make a copy
+ info = ((AppInfo) info).makeWorkspaceItem();
+ d.dragInfo = info;
+ }
+ view = mLauncher.createShortcut(cellLayout, (WorkspaceItemInfo) info);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
+ (FolderInfo) info);
+ break;
+ default:
+ throw new IllegalStateException("Unknown item type: " + info.itemType);
}
// First we find the cell nearest to point at which the item is
@@ -2798,10 +2816,27 @@
}
/**
+ * Removed widget from workspace by appWidgetId
+ * @param appWidgetId
+ */
+ public void removeWidget(int appWidgetId) {
+ mapOverItems((info, view) -> {
+ if (info instanceof LauncherAppWidgetInfo) {
+ LauncherAppWidgetInfo appWidgetInfo = (LauncherAppWidgetInfo) info;
+ if (appWidgetInfo.appWidgetId == appWidgetId) {
+ mLauncher.removeItem(view, appWidgetInfo, true);
+ return true;
+ }
+ }
+ return false;
+ });
+ }
+
+ /**
* Removes all folder listeners
*/
public void removeFolderListeners() {
- mapOverItems(false, new ItemOperator() {
+ mapOverItems(new ItemOperator() {
@Override
public boolean evaluate(ItemInfo info, View view) {
if (view instanceof FolderIcon) {
@@ -2848,7 +2883,7 @@
@Override
public boolean scrollLeft() {
boolean result = false;
- if (!workspaceInModalState() && !mIsSwitchingState) {
+ if (!mIsSwitchingState && workspaceInScrollableState()) {
result = super.scrollLeft();
}
Folder openFolder = Folder.getOpen(mLauncher);
@@ -2861,7 +2896,7 @@
@Override
public boolean scrollRight() {
boolean result = false;
- if (!workspaceInModalState() && !mIsSwitchingState) {
+ if (!mIsSwitchingState && workspaceInScrollableState()) {
result = super.scrollRight();
}
Folder openFolder = Folder.getOpen(mLauncher);
@@ -2953,7 +2988,7 @@
public View getFirstMatch(final ItemOperator operator) {
final View[] value = new View[1];
- mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
+ mapOverItems(new ItemOperator() {
@Override
public boolean evaluate(ItemInfo info, View v) {
if (operator.evaluate(info, v)) {
@@ -2976,7 +3011,7 @@
final View[] matches = new View[operators.length];
// For efficiency, the outer loop should be CellLayout.
for (CellLayout cellLayout : cellLayouts) {
- mapOverCellLayout(MAP_NO_RECURSE, cellLayout, (info, v) -> {
+ mapOverCellLayout(cellLayout, (info, v) -> {
for (int i = 0; i < operators.length; ++i) {
if (matches[i] == null && operators[i].evaluate(info, v)) {
matches[i] = v;
@@ -3001,7 +3036,7 @@
}
void clearDropTargets() {
- mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
+ mapOverItems(new ItemOperator() {
@Override
public boolean evaluate(ItemInfo info, View v) {
if (v instanceof DropTarget) {
@@ -3046,10 +3081,12 @@
} else if (itemToRemove.container >= 0) {
// The item may belong to a folder.
View parent = idToViewMap.get(itemToRemove.container);
- if (parent != null) {
+ if (parent instanceof FolderIcon) {
FolderInfo folderInfo = (FolderInfo) parent.getTag();
- folderInfo.prepareAutoUpdate();
folderInfo.remove((WorkspaceItemInfo) itemToRemove, false);
+ if (((FolderIcon) parent).getFolder().isOpen()) {
+ ((FolderIcon) parent).getFolder().close(false /* animate */);
+ }
}
}
}
@@ -3073,18 +3110,17 @@
/**
* Map the operator over the shortcuts and widgets, return the first-non-null value.
*
- * @param recurse true: iterate over folder children. false: op get the folders themselves.
* @param op the operator to map over the shortcuts
*/
- public void mapOverItems(boolean recurse, ItemOperator op) {
+ public void mapOverItems(ItemOperator op) {
for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) {
- if (mapOverCellLayout(recurse, layout, op)) {
+ if (mapOverCellLayout(layout, op)) {
return;
}
}
}
- private boolean mapOverCellLayout(boolean recurse, CellLayout layout, ItemOperator op) {
+ private boolean mapOverCellLayout(CellLayout layout, ItemOperator op) {
// TODO(b/128460496) Potential race condition where layout is not yet loaded
if (layout == null) {
return false;
@@ -3094,103 +3130,68 @@
final int itemCount = container.getChildCount();
for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
View item = container.getChildAt(itemIdx);
- ItemInfo info = (ItemInfo) item.getTag();
- if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) {
- FolderIcon folder = (FolderIcon) item;
- ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder();
- // map over all the children in the folder
- final int childCount = folderChildren.size();
- for (int childIdx = 0; childIdx < childCount; childIdx++) {
- View child = folderChildren.get(childIdx);
- info = (ItemInfo) child.getTag();
- if (op.evaluate(info, child)) {
- return true;
- }
- }
- } else {
- if (op.evaluate(info, item)) {
- return true;
- }
+ if (op.evaluate((ItemInfo) item.getTag(), item)) {
+ return true;
}
}
return false;
}
void updateShortcuts(ArrayList<WorkspaceItemInfo> shortcuts) {
- int total = shortcuts.size();
- final HashSet<WorkspaceItemInfo> updates = new HashSet<>(total);
- final IntSet folderIds = new IntSet();
+ final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts);
+ ItemOperator op = (info, v) -> {
+ if (v instanceof BubbleTextView && updates.contains(info)) {
+ WorkspaceItemInfo si = (WorkspaceItemInfo) info;
+ BubbleTextView shortcut = (BubbleTextView) v;
+ Drawable oldIcon = shortcut.getIcon();
+ boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
+ && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
+ shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState);
+ } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
+ ((FolderIcon) v).updatePreviewItems(updates::contains);
+ }
- for (int i = 0; i < total; i++) {
- WorkspaceItemInfo s = shortcuts.get(i);
- updates.add(s);
- folderIds.add(s.container);
+ // Iterate all items
+ return false;
+ };
+
+ mapOverItems(op);
+ Folder openFolder = Folder.getOpen(mLauncher);
+ if (openFolder != null) {
+ openFolder.iterateOverItems(op);
}
-
- mapOverItems(MAP_RECURSE, new ItemOperator() {
- @Override
- public boolean evaluate(ItemInfo info, View v) {
- if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView &&
- updates.contains(info)) {
- WorkspaceItemInfo si = (WorkspaceItemInfo) info;
- BubbleTextView shortcut = (BubbleTextView) v;
- Drawable oldIcon = shortcut.getIcon();
- boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
- && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
- shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState);
- }
- // process all the shortcuts
- return false;
- }
- });
-
- // Update folder icons
- mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
- @Override
- public boolean evaluate(ItemInfo info, View v) {
- if (info instanceof FolderInfo && folderIds.contains(info.id)) {
- ((FolderInfo) info).itemsChanged(false);
- }
- // process all the shortcuts
- return false;
- }
- });
}
public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
final PackageUserKey packageUserKey = new PackageUserKey(null, null);
- final IntSet folderIds = new IntSet();
- mapOverItems(MAP_RECURSE, new ItemOperator() {
- @Override
- public boolean evaluate(ItemInfo info, View v) {
- if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
- if (!packageUserKey.updateFromItemInfo(info)
- || updatedDots.test(packageUserKey)) {
- ((BubbleTextView) v).applyDotState(info, true /* animate */);
- folderIds.add(info.container);
- }
- }
- // process all the shortcuts
- return false;
- }
- });
+ Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
+ || updatedDots.test(packageUserKey);
- // Update folder icons
- mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
- @Override
- public boolean evaluate(ItemInfo info, View v) {
- if (info instanceof FolderInfo && folderIds.contains(info.id)
- && v instanceof FolderIcon) {
+ ItemOperator op = (info, v) -> {
+ if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
+ if (matcher.test(info)) {
+ ((BubbleTextView) v).applyDotState(info, true /* animate */);
+ }
+ } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
+ FolderInfo fi = (FolderInfo) info;
+ if (fi.contents.stream().anyMatch(matcher)) {
FolderDotInfo folderDotInfo = new FolderDotInfo();
- for (WorkspaceItemInfo si : ((FolderInfo) info).contents) {
+ for (WorkspaceItemInfo si : fi.contents) {
folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si));
}
((FolderIcon) v).setDotInfo(folderDotInfo);
}
- // process all the shortcuts
- return false;
}
- });
+
+ // process all the shortcuts
+ return false;
+ };
+
+ mapOverItems(op);
+ Folder folder = Folder.getOpen(mLauncher);
+ if (folder != null) {
+ folder.iterateOverItems(op);
+ }
}
public void removeAbandonedPromise(String packageName, UserHandle user) {
@@ -3202,21 +3203,25 @@
}
public void updateRestoreItems(final HashSet<ItemInfo> updates) {
- mapOverItems(MAP_RECURSE, new ItemOperator() {
- @Override
- public boolean evaluate(ItemInfo info, View v) {
- if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView
- && updates.contains(info)) {
- ((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */);
- } else if (v instanceof PendingAppWidgetHostView
- && info instanceof LauncherAppWidgetInfo
- && updates.contains(info)) {
- ((PendingAppWidgetHostView) v).applyState();
- }
- // process all the shortcuts
- return false;
+ ItemOperator op = (info, v) -> {
+ if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView
+ && updates.contains(info)) {
+ ((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */);
+ } else if (v instanceof PendingAppWidgetHostView
+ && info instanceof LauncherAppWidgetInfo
+ && updates.contains(info)) {
+ ((PendingAppWidgetHostView) v).applyState();
+ } else if (v instanceof FolderIcon && info instanceof FolderInfo) {
+ ((FolderIcon) v).updatePreviewItems(updates::contains);
}
- });
+ // process all the shortcuts
+ return false;
+ };
+ mapOverItems(op);
+ Folder folder = Folder.getOpen(mLauncher);
+ if (folder != null) {
+ folder.iterateOverItems(op);
+ }
}
public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) {
@@ -3240,7 +3245,7 @@
} else {
// widgetRefresh will automatically run when the packages are updated.
// For now just update the progress bars
- mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
+ mapOverItems(new ItemOperator() {
@Override
public boolean evaluate(ItemInfo info, View view) {
if (view instanceof PendingAppWidgetHostView
@@ -3256,6 +3261,10 @@
}
}
+ public boolean isOverlayShown() {
+ return mOverlayShown;
+ }
+
void moveToDefaultScreen() {
int page = DEFAULT_PAGE;
if (!workspaceInModalState() && getNextPage() != page) {
@@ -3360,7 +3369,7 @@
mRefreshPending = false;
ArrayList<PendingAppWidgetHostView> views = new ArrayList<>(mInfos.size());
- mapOverItems(MAP_NO_RECURSE, (info, view) -> {
+ mapOverItems((info, view) -> {
if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
views.add((PendingAppWidgetHostView) view);
}
diff --git a/src/com/android/launcher3/WorkspaceItemInfo.java b/src/com/android/launcher3/WorkspaceItemInfo.java
index 5a2373b..23795c5 100644
--- a/src/com/android/launcher3/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/WorkspaceItemInfo.java
@@ -16,17 +16,23 @@
package com.android.launcher3;
+import android.app.Person;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.text.TextUtils;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.util.ContentWriter;
+import java.util.Arrays;
+
/**
* Represents a launchable icon on the workspaces and in folders.
*/
@@ -44,24 +50,26 @@
* The icon was added as an auto-install app, and is not ready to be used. This flag can't
* be present along with {@link #FLAG_RESTORED_ICON}, and is set during default layout
* parsing.
+ *
+ * OR this icon was added due to it being an active install session created by the user.
*/
- public static final int FLAG_AUTOINSTALL_ICON = 2; //0B10;
+ public static final int FLAG_AUTOINSTALL_ICON = 1 << 1;
/**
* The icon is being installed. If {@link #FLAG_RESTORED_ICON} or {@link #FLAG_AUTOINSTALL_ICON}
* is set, then the icon is either being installed or is in a broken state.
*/
- public static final int FLAG_INSTALL_SESSION_ACTIVE = 4; // 0B100;
+ public static final int FLAG_INSTALL_SESSION_ACTIVE = 1 << 2;
/**
* Indicates that the widget restore has started.
*/
- public static final int FLAG_RESTORE_STARTED = 8; //0B1000;
+ public static final int FLAG_RESTORE_STARTED = 1 << 3;
/**
* Web UI supported.
*/
- public static final int FLAG_SUPPORTS_WEB_UI = 16; //0B10000;
+ public static final int FLAG_SUPPORTS_WEB_UI = 1 << 4;
/**
* The intent used to start the application.
@@ -83,10 +91,17 @@
public int status;
/**
+ * A set of person's Id associated with the WorkspaceItemInfo, this is only used if the item
+ * represents a deep shortcut.
+ */
+ @NonNull private String[] personKeys = Utilities.EMPTY_STRING_ARRAY;
+
+ /**
* The installation progress [0-100] of the package that this shortcut represents.
*/
private int mInstallProgress;
+
public WorkspaceItemInfo() {
itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
}
@@ -98,6 +113,7 @@
iconResource = info.iconResource;
status = info.status;
mInstallProgress = info.mInstallProgress;
+ personKeys = info.personKeys.clone();
}
/** TODO: Remove this. It's only called by ApplicationInfo.makeWorkspaceItem. */
@@ -175,6 +191,10 @@
runtimeStatusFlags |= FLAG_DISABLED_BY_PUBLISHER;
}
disabledMessage = shortcutInfo.getDisabledMessage();
+
+ Person[] persons = UiFactory.getPersons(shortcutInfo);
+ personKeys = persons.length == 0 ? Utilities.EMPTY_STRING_ARRAY
+ : Arrays.stream(persons).map(Person::getKey).sorted().toArray(String[]::new);
}
/** Returns the WorkspaceItemInfo id associated with the deep shortcut. */
@@ -183,11 +203,16 @@
getIntent().getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID) : null;
}
+ @NonNull
+ public String[] getPersonKeys() {
+ return personKeys;
+ }
+
@Override
public ComponentName getTargetComponent() {
ComponentName cn = super.getTargetComponent();
if (cn == null && (itemType == Favorites.ITEM_TYPE_SHORTCUT
- || hasStatusFlag(FLAG_SUPPORTS_WEB_UI))) {
+ || hasStatusFlag(FLAG_SUPPORTS_WEB_UI|FLAG_AUTOINSTALL_ICON|FLAG_RESTORED_ICON))) {
// Legacy shortcuts and promise icons with web UI may not have a componentName but just
// a packageName. In that case create a dummy componentName instead of adding additional
// check everywhere.
@@ -196,4 +221,9 @@
}
return cn;
}
+
+ @Override
+ public ItemInfoWithIcon clone() {
+ return new WorkspaceItemInfo(this);
+ }
}
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 40c6b5f..7a7e1fe 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -38,7 +38,6 @@
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
/**
@@ -96,14 +95,13 @@
propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
if (!hotseat.getRotationMode().isTransposed) {
- // Set the hotseat's pivot point to match the workspace's, so that it scales together.
- DragLayer dragLayer = mLauncher.getDragLayer();
- float[] workspacePivot =
- new float[]{ mWorkspace.getPivotX(), mWorkspace.getPivotY() };
- dragLayer.getDescendantCoordRelativeToSelf(mWorkspace, workspacePivot);
- dragLayer.mapCoordInSelfToDescendant(hotseat, workspacePivot);
- hotseat.setPivotX(workspacePivot[0]);
- hotseat.setPivotY(workspacePivot[1]);
+ // Set the hotseat's pivot point to match the workspace's, so that it scales
+ // together. Since both hotseat and workspace can move, transform the point
+ // manually instead of using dragLayer.getDescendantCoordRelativeToSelf and
+ // related methods.
+ hotseat.setPivotY(mWorkspace.getPivotY() + mWorkspace.getTop() - hotseat.getTop());
+ hotseat.setPivotX(mWorkspace.getPivotX()
+ + mWorkspace.getLeft() - hotseat.getLeft());
}
float hotseatScale = hotseatScaleAndTranslation.scale;
Interpolator hotseatScaleInterpolator = builder.getInterpolator(ANIM_HOTSEAT_SCALE,
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index fd4df52..0c12c60 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -1,5 +1,7 @@
package com.android.launcher3.accessibility;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
+
import static com.android.launcher3.LauncherState.NORMAL;
import android.app.AlertDialog;
@@ -30,16 +32,17 @@
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.PendingAddItemInfo;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.Workspace;
+import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.dragndrop.DragController.DragListener;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
+import com.android.launcher3.keyboard.CustomActionsPopup;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.popup.PopupContainerWithArrow;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
@@ -115,7 +118,7 @@
// If the request came from keyboard, do not add custom shortcuts as that is already
// exposed as a direct shortcut
- if (!fromKeyboard && DeepShortcutManager.supportsShortcuts(item)) {
+ if (!fromKeyboard && ShortcutUtil.supportsShortcuts(item)) {
info.addAction(mActions.get(NotificationListener.getInstanceIfConnected() != null
? SHORTCUTS_AND_NOTIFICATIONS : DEEP_SHORTCUTS));
}
@@ -163,6 +166,13 @@
}
public boolean performAction(final View host, final ItemInfo item, int action) {
+ if (action == ACTION_LONG_CLICK && ShortcutUtil.isDeepShortcut(item)) {
+ CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host);
+ if (popup.canShow()) {
+ popup.show();
+ return true;
+ }
+ }
if (action == MOVE) {
beginAccessibleDrag(host, item);
} else if (action == ADD_TO_WORKSPACE) {
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index ea9b077..2d5b040 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -26,14 +26,18 @@
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
-
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.AppInfo;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
@@ -45,11 +49,12 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusedItemDecorator;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.MultiValueAlpha;
@@ -59,13 +64,6 @@
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.SpringRelativeLayout;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
/**
* The all apps view container.
*/
@@ -199,7 +197,10 @@
// The AllAppsContainerView houses the QSB and is hence visible from the Workspace
// Overview states. We shouldn't intercept for the scrubber in these cases.
- if (!mLauncher.isInState(LauncherState.ALL_APPS)) return false;
+ if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
+ mTouchHandler = null;
+ return false;
+ }
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
AllAppsRecyclerView rv = getActiveRecyclerView();
@@ -218,6 +219,16 @@
@Override
public boolean onTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ AllAppsRecyclerView rv = getActiveRecyclerView();
+ if (rv != null && rv.getScrollbar()
+ .isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) {
+ mTouchHandler = rv.getScrollbar();
+ } else {
+ mTouchHandler = null;
+ }
+ }
+
if (mTouchHandler != null) {
mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
return true;
@@ -297,7 +308,11 @@
@Override
public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
- // This is filled in {@link AllAppsRecyclerView}
+ if (getApps().hasFilter()) {
+ targetParent.containerType = ContainerType.SEARCHRESULT;
+ } else {
+ targetParent.containerType = ContainerType.ALLAPPS;
+ }
}
@Override
@@ -307,6 +322,7 @@
+ grid.cellLayoutPaddingLeftRightPx;
for (int i = 0; i < mAH.length; i++) {
+ mAH[i].adapter.setAppsPerRow(grid.inv.numAllAppsColumns);
mAH[i].padding.bottom = insets.bottom;
mAH[i].padding.left = mAH[i].padding.right = leftRightPadding;
mAH[i].applyPadding();
@@ -623,23 +639,4 @@
return super.performAccessibilityAction(action, arguments);
}
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_START_TAG, "AllAppsContainerView.dispatchTouchEvent " + ev);
- }
- final boolean result = super.dispatchTouchEvent(ev);
- switch (ev.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- if (result) mAllAppsStore.enableDeferUpdates(
- AllAppsStore.DEFER_UPDATES_USER_INTERACTION);
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- mAllAppsStore.disableDeferUpdates(AllAppsStore.DEFER_UPDATES_USER_INTERACTION);
- break;
- }
- return result;
- }
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 3cfa0b1..bb21268 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -180,7 +180,7 @@
private final GridLayoutManager mGridLayoutMgr;
private final GridSpanSizer mGridSizer;
- private final int mAppsPerRow;
+ private int mAppsPerRow;
private BindViewCallback mBindViewCallback;
private OnFocusChangeListener mIconFocusListener;
@@ -200,7 +200,11 @@
mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
mLayoutInflater = LayoutInflater.from(launcher);
- mAppsPerRow = mLauncher.getDeviceProfile().inv.numColumns;
+ setAppsPerRow(mLauncher.getDeviceProfile().inv.numAllAppsColumns);
+ }
+
+ public void setAppsPerRow(int appsPerRow) {
+ mAppsPerRow = appsPerRow;
mGridLayoutMgr.setSpanCount(mAppsPerRow);
}
diff --git a/src/com/android/launcher3/allapps/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java
index 69068c6..5b73940 100644
--- a/src/com/android/launcher3/allapps/AllAppsPagedView.java
+++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java
@@ -81,4 +81,9 @@
public boolean hasOverlappingRendering() {
return false;
}
+
+ @Override
+ protected boolean isVerticalScrollable() {
+ return false;
+ }
}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index a0e9dc5..f82e380 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -26,13 +26,15 @@
import android.view.MotionEvent;
import android.view.View;
+import androidx.recyclerview.widget.RecyclerView;
+
import com.android.launcher3.BaseRecyclerView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AppsGridLayoutManager;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -41,8 +43,6 @@
import java.util.List;
-import androidx.recyclerview.widget.RecyclerView;
-
/**
* A RecyclerView with custom fast scroll support for the all apps view.
*/
@@ -114,6 +114,13 @@
if (mScrollbar != null) {
mScrollbar.reattachThumbToScroll();
}
+ if (getLayoutManager() instanceof AppsGridLayoutManager) {
+ AppsGridLayoutManager layoutManager = (AppsGridLayoutManager) getLayoutManager();
+ if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
+ // We are at the top, so don't scrollToPosition (would cause unnecessary relayout).
+ return;
+ }
+ }
scrollToPosition(0);
}
@@ -420,13 +427,4 @@
public boolean hasOverlappingRendering() {
return false;
}
-
- @Override
- public void onScrollStateChanged(int state) {
- super.onScrollStateChanged(state);
-
- if (state == SCROLL_STATE_IDLE) {
- AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
- }
- }
}
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 160042e..c4b2f68 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -15,6 +15,9 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.AppInfo.COMPONENT_KEY_COMPARATOR;
+import static com.android.launcher3.AppInfo.EMPTY_ARRAY;
+
import android.view.View;
import android.view.ViewGroup;
@@ -26,8 +29,7 @@
import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
+import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -39,33 +41,37 @@
// Defer updates flag used to defer all apps updates to the next draw.
public static final int DEFER_UPDATES_NEXT_DRAW = 1 << 0;
- // Defer updates flag used to defer all apps updates while the user interacts with all apps.
- public static final int DEFER_UPDATES_USER_INTERACTION = 1 << 1;
// Defer updates flag used to defer all apps updates by a test's request.
- public static final int DEFER_UPDATES_TEST = 1 << 2;
+ public static final int DEFER_UPDATES_TEST = 1 << 1;
private PackageUserKey mTempKey = new PackageUserKey(null, null);
- private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
+ private AppInfo mTempInfo = new AppInfo();
+
+ private AppInfo[] mApps = EMPTY_ARRAY;
+
private final List<OnUpdateListener> mUpdateListeners = new ArrayList<>();
private final ArrayList<ViewGroup> mIconContainers = new ArrayList<>();
private int mDeferUpdatesFlags = 0;
private boolean mUpdatePending = false;
- public Collection<AppInfo> getApps() {
- return mComponentToAppMap.values();
+ public AppInfo[] getApps() {
+ return mApps;
}
/**
* Sets the current set of apps.
*/
- public void setApps(List<AppInfo> apps) {
- mComponentToAppMap.clear();
- addOrUpdateApps(apps);
+ public void setApps(AppInfo[] apps) {
+ mApps = apps;
+ notifyUpdate();
}
public AppInfo getApp(ComponentKey key) {
- return mComponentToAppMap.get(key);
+ mTempInfo.componentName = key.componentName;
+ mTempInfo.user = key.user;
+ int index = Arrays.binarySearch(mApps, mTempInfo, COMPONENT_KEY_COMPARATOR);
+ return index < 0 ? null : mApps[index];
}
public void enableDeferUpdates(int flag) {
@@ -80,31 +86,14 @@
}
}
+ public void disableDeferUpdatesSilently(int flag) {
+ mDeferUpdatesFlags &= ~flag;
+ }
+
public int getDeferUpdatesFlags() {
return mDeferUpdatesFlags;
}
- /**
- * Adds or updates existing apps in the list
- */
- public void addOrUpdateApps(List<AppInfo> apps) {
- for (AppInfo app : apps) {
- mComponentToAppMap.put(app.toComponentKey(), app);
- }
- notifyUpdate();
- }
-
- /**
- * Removes some apps from the list.
- */
- public void removeApps(List<AppInfo> apps) {
- for (AppInfo app : apps) {
- mComponentToAppMap.remove(app.toComponentKey());
- }
- notifyUpdate();
- }
-
-
private void notifyUpdate() {
if (mDeferUpdatesFlags != 0) {
mUpdatePending = true;
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 4683893..08ce9c2 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -2,11 +2,10 @@
import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
@@ -17,7 +16,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.util.FloatProperty;
-import android.util.Log;
import android.view.animation.Interpolator;
import com.android.launcher3.DeviceProfile;
@@ -31,7 +29,6 @@
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.anim.SpringObjectAnimator;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ScrimView;
@@ -135,15 +132,6 @@
} else {
mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0);
}
-
- if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
- // Translate hotseat with the shelf until reaching overview.
- float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
- if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
- float hotseatShift = (progress - overviewProgress) * mShiftRange;
- mLauncher.getHotseat().setTranslationY(hotseatShift);
- }
- }
}
public float getProgress() {
@@ -168,10 +156,6 @@
@Override
public void setStateWithAnimation(LauncherState toState,
AnimatorSetBuilder builder, AnimationConfig config) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG,
- "setStateWithAnimation " + toState.getClass().getSimpleName());
- }
float targetProgress = toState.getVerticalProgress(mLauncher);
if (Float.compare(mProgress, targetProgress) == 0) {
setAlphas(toState, config, builder);
@@ -212,13 +196,14 @@
PropertySetter setter = config == null ? NO_ANIM_PROPERTY_SETTER
: config.getPropertySetter(builder);
boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
- boolean hasContent = (visibleElements & ALL_APPS_CONTENT) != 0;
+ boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0;
Interpolator allAppsFade = builder.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
- setter.setViewAlpha(mAppsView.getContentView(), hasContent ? 1 : 0, allAppsFade);
- setter.setViewAlpha(mAppsView.getScrollBar(), hasContent ? 1 : 0, allAppsFade);
- mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra, hasContent, setter,
- allAppsFade);
+ Interpolator headerFade = builder.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade);
+ setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
+ setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
+ mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra, hasAllAppsContent,
+ setter, headerFade, allAppsFade);
mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade);
setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA,
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 2ad92e1..0c4be62 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -21,16 +21,13 @@
import com.android.launcher3.AppInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.AlphabeticIndexCompat;
import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LabelComparator;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -145,9 +142,7 @@
// The of ordered component names as a result of a search query
private ArrayList<ComponentKey> mSearchResults;
- private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>();
private AllAppsGridAdapter mAdapter;
- private AlphabeticIndexCompat mIndexer;
private AppInfoComparator mAppNameComparator;
private final int mNumAppsPerRow;
private int mNumAppRowsInAdapter;
@@ -156,7 +151,6 @@
public AlphabeticalAppsList(Context context, AllAppsStore appsStore, boolean isWork) {
mAllAppsStore = appsStore;
mLauncher = Launcher.getLauncher(context);
- mIndexer = new AlphabeticIndexCompat(context);
mAppNameComparator = new AppInfoComparator(context);
mIsWork = isWork;
mNumAppsPerRow = mLauncher.getDeviceProfile().inv.numColumns;
@@ -263,7 +257,7 @@
TreeMap<String, ArrayList<AppInfo>> sectionMap = new TreeMap<>(new LabelComparator());
for (AppInfo info : mApps) {
// Add the section to the cache
- String sectionName = getAndUpdateCachedSectionName(info.title);
+ String sectionName = info.sectionName;
// Add it to the mapping
ArrayList<AppInfo> sectionApps = sectionMap.get(sectionName);
@@ -279,12 +273,6 @@
for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
mApps.addAll(entry.getValue());
}
- } else {
- // Just compute the section headers for use below
- for (AppInfo info : mApps) {
- // Add the section to the cache
- getAndUpdateCachedSectionName(info.title);
- }
}
// Recompose the set of adapter items from the current set of apps
@@ -301,11 +289,6 @@
}
private void refreshRecyclerView() {
- if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_START_TAG,
- "refreshRecyclerView @ " + android.util.Log.getStackTraceString(
- new Throwable()));
- }
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
@@ -325,7 +308,7 @@
// Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
// ordered set of sections
for (AppInfo info : getFiltersAppInfos()) {
- String sectionName = getAndUpdateCachedSectionName(info.title);
+ String sectionName = info.sectionName;
// Create a new section if the section names do not match
if (!sectionName.equals(lastSectionName)) {
@@ -433,18 +416,4 @@
}
return result;
}
-
- /**
- * Returns the cached section name for the given title, recomputing and updating the cache if
- * the title has no cached section name.
- */
- private String getAndUpdateCachedSectionName(CharSequence title) {
- String sectionName = mCachedSectionNames.get(title);
- if (sectionName == null) {
- sectionName = mIndexer.computeSectionName(title);
- mCachedSectionNames.put(title, sectionName);
- }
- return sectionName;
- }
-
}
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderRow.java b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
index 922e4f1..f899587 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderRow.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
@@ -46,8 +46,8 @@
*/
boolean hasVisibleContent();
- void setContentVisibility(boolean hasHeaderExtra, boolean hasContent,
- PropertySetter setter, Interpolator fadeInterpolator);
+ void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
+ PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade);
/**
* Scrolls the content vertically.
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 66dced9..42a0eee 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -27,6 +27,10 @@
import android.view.animation.Interpolator;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
@@ -40,10 +44,6 @@
import java.util.ArrayList;
import java.util.Map;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.RecyclerView;
-
public class FloatingHeaderView extends LinearLayout implements
ValueAnimator.AnimatorUpdateListener, PluginListener<AllAppsRow>, Insettable,
OnHeightUpdatedListener {
@@ -363,14 +363,14 @@
p.y = getTop() - mCurrentRV.getTop() - mParent.getTop();
}
- public void setContentVisibility(boolean hasHeader, boolean hasContent, PropertySetter setter,
- Interpolator fadeInterpolator) {
+ public void setContentVisibility(boolean hasHeader, boolean hasAllAppsContent,
+ PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
for (FloatingHeaderRow row : mAllRows) {
- row.setContentVisibility(hasHeader, hasContent, setter, fadeInterpolator);
+ row.setContentVisibility(hasHeader, hasAllAppsContent, setter, headerFade, allAppsFade);
}
- allowTouchForwarding(hasContent);
- setter.setFloat(mTabLayout, ALPHA, hasContent ? 1 : 0, fadeInterpolator);
+ allowTouchForwarding(hasAllAppsContent);
+ setter.setFloat(mTabLayout, ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
}
protected void allowTouchForwarding(boolean allow) {
diff --git a/src/com/android/launcher3/allapps/PluginHeaderRow.java b/src/com/android/launcher3/allapps/PluginHeaderRow.java
index b283ff4..535ef54 100644
--- a/src/com/android/launcher3/allapps/PluginHeaderRow.java
+++ b/src/com/android/launcher3/allapps/PluginHeaderRow.java
@@ -64,10 +64,10 @@
}
@Override
- public void setContentVisibility(boolean hasHeaderExtra, boolean hasContent,
- PropertySetter setter, Interpolator fadeInterpolator) {
+ public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
+ PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
// Don't use setViewAlpha as we want to control the visibility ourselves.
- setter.setFloat(mView, ALPHA, hasContent ? 1 : 0, fadeInterpolator);
+ setter.setFloat(mView, ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
}
@Override
diff --git a/src/com/android/launcher3/anim/AlphaUpdateListener.java b/src/com/android/launcher3/anim/AlphaUpdateListener.java
index 8ac9d66..eabd283 100644
--- a/src/com/android/launcher3/anim/AlphaUpdateListener.java
+++ b/src/com/android/launcher3/anim/AlphaUpdateListener.java
@@ -27,7 +27,7 @@
*/
public class AlphaUpdateListener extends AnimationSuccessListener
implements AnimatorUpdateListener {
- private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
+ public static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
private View mView;
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 2c440bb..4a52795 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -26,15 +26,15 @@
import android.animation.ValueAnimator;
import android.util.Log;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringAnimation;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringAnimation;
-
/**
* Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators
* and durations.
@@ -250,6 +250,17 @@
}
}
+ /**
+ * Sets mOnCancelRunnable = null before dispatching the cancel and restoring the runnable. This
+ * is intended to be used only if you need to cancel but want to defer cleaning up yourself.
+ */
+ public void dispatchOnCancelWithoutCancelRunnable() {
+ Runnable onCancel = mOnCancelRunnable;
+ setOnCancelRunnable(null);
+ dispatchOnCancel();
+ setOnCancelRunnable(onCancel);
+ }
+
public void dispatchOnCancel() {
dispatchOnCancelRecursively(mAnim);
}
@@ -283,10 +294,6 @@
mOnCancelRunnable = runnable;
}
- public Runnable getOnCancelRunnable() {
- return mOnCancelRunnable;
- }
-
public void skipToEnd() {
mSkipToEnd = true;
for (SpringAnimation spring : mSprings) {
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index 52a896e..cd30dea 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -39,6 +39,8 @@
public static final int ANIM_OVERVIEW_TRANSLATE_Y = 8;
public static final int ANIM_OVERVIEW_FADE = 9;
public static final int ANIM_ALL_APPS_FADE = 10;
+ public static final int ANIM_OVERVIEW_SCRIM_FADE = 11;
+ public static final int ANIM_ALL_APPS_HEADER_FADE = 12; // e.g. predictions
public static final int FLAG_DONT_ANIMATE_OVERVIEW = 1 << 0;
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index b169cb8..fccc120 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -16,8 +16,9 @@
package com.android.launcher3.anim;
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import android.content.Context;
import android.graphics.Path;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
@@ -38,6 +39,7 @@
public static final Interpolator LINEAR = new LinearInterpolator();
public static final Interpolator ACCEL = new AccelerateInterpolator();
+ public static final Interpolator ACCEL_0_75 = new AccelerateInterpolator(0.75f);
public static final Interpolator ACCEL_1_5 = new AccelerateInterpolator(1.5f);
public static final Interpolator ACCEL_2 = new AccelerateInterpolator(2);
@@ -47,6 +49,7 @@
public static final Interpolator DEACCEL_2 = new DecelerateInterpolator(2);
public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f);
public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f);
+ public static final Interpolator DEACCEL_5 = new DecelerateInterpolator(5f);
public static final Interpolator ACCEL_DEACCEL = new AccelerateDecelerateInterpolator();
@@ -144,7 +147,8 @@
public static Interpolator clampToProgress(Interpolator interpolator, float lowerBound,
float upperBound) {
if (upperBound <= lowerBound) {
- throw new IllegalArgumentException("lowerBound must be less than upperBound");
+ throw new IllegalArgumentException(String.format(
+ "lowerBound (%f) must be less than upperBound (%f)", lowerBound, upperBound));
}
return t -> {
if (t < lowerBound) {
@@ -187,13 +191,13 @@
* @param totalDistancePx The distance against which progress is calculated.
*/
public OvershootParams(float startProgress, float overshootPastProgress,
- float endProgress, float velocityPxPerMs, int totalDistancePx) {
+ float endProgress, float velocityPxPerMs, int totalDistancePx, Context context) {
velocityPxPerMs = Math.abs(velocityPxPerMs);
start = startProgress;
int startPx = (int) (start * totalDistancePx);
// Overshoot by about half a frame.
float overshootBy = OVERSHOOT_FACTOR * velocityPxPerMs *
- SINGLE_FRAME_MS / totalDistancePx / 2;
+ getSingleFrameMs(context) / totalDistancePx / 2;
overshootBy = Utilities.boundToRange(overshootBy, 0.02f, 0.15f);
end = overshootPastProgress + overshootBy;
int endPx = (int) (end * totalDistancePx);
diff --git a/src/com/android/launcher3/anim/SpringAnimationBuilder.java b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
new file mode 100644
index 0000000..0f34c1e
--- /dev/null
+++ b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2019 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.anim;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.util.FloatProperty;
+
+import com.android.launcher3.util.DefaultDisplay;
+
+import androidx.annotation.FloatRange;
+import androidx.dynamicanimation.animation.SpringForce;
+
+/**
+ * Utility class to build an object animator which follows the same path as a spring animation for
+ * an underdamped spring.
+ */
+public class SpringAnimationBuilder<T> extends FloatProperty<T> {
+
+ private final T mTarget;
+ private final FloatProperty<T> mProperty;
+
+ private float mStartValue;
+ private float mEndValue;
+ private float mVelocity = 0;
+
+ private float mStiffness = SpringForce.STIFFNESS_MEDIUM;
+ private float mDampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
+ private float mMinVisibleChange = 1;
+
+ // Multiplier to the min visible change value for value threshold
+ private static final float THRESHOLD_MULTIPLIER = 0.65f;
+
+ /**
+ * The spring equation is given as
+ * x = e^(-beta*t/2) * (a cos(gamma * t) + b sin(gamma * t)
+ * v = e^(-beta*t/2) * ((2 * a * gamma + beta * b) * sin(gamma * t)
+ * + (a * beta - 2 * b * gamma) * cos(gamma * t)) / 2
+ *
+ * a = x(0)
+ * b = beta * x(0) / (2 * gamma) + v(0) / gamma
+ */
+ private double beta;
+ private double gamma;
+
+ private double a, b;
+ private double va, vb;
+
+ // Threshold for velocity and value to determine when it's reasonable to assume that the spring
+ // is approximately at rest.
+ private double mValueThreshold;
+ private double mVelocityThreshold;
+
+ private float mCurrentTime = 0;
+
+ public SpringAnimationBuilder(T target, FloatProperty<T> property) {
+ super("dynamic-spring-property");
+ mTarget = target;
+ mProperty = property;
+
+ mStartValue = mProperty.get(target);
+ }
+
+ public SpringAnimationBuilder<T> setEndValue(float value) {
+ mEndValue = value;
+ return this;
+ }
+
+ public SpringAnimationBuilder<T> setStartValue(float value) {
+ mStartValue = value;
+ return this;
+ }
+
+ public SpringAnimationBuilder<T> setValues(float... values) {
+ if (values.length > 1) {
+ mStartValue = values[0];
+ mEndValue = values[values.length - 1];
+ } else {
+ mEndValue = values[0];
+ }
+ return this;
+ }
+
+ public SpringAnimationBuilder<T> setStiffness(
+ @FloatRange(from = 0.0, fromInclusive = false) float stiffness) {
+ if (stiffness <= 0) {
+ throw new IllegalArgumentException("Spring stiffness constant must be positive.");
+ }
+ mStiffness = stiffness;
+ return this;
+ }
+
+ public SpringAnimationBuilder<T> setDampingRatio(
+ @FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false)
+ float dampingRatio) {
+ if (dampingRatio <= 0 || dampingRatio >= 1) {
+ throw new IllegalArgumentException("Damping ratio must be between 0 and 1");
+ }
+ mDampingRatio = dampingRatio;
+ return this;
+ }
+
+ public SpringAnimationBuilder<T> setMinimumVisibleChange(
+ @FloatRange(from = 0.0, fromInclusive = false) float minimumVisibleChange) {
+ if (minimumVisibleChange <= 0) {
+ throw new IllegalArgumentException("Minimum visible change must be positive.");
+ }
+ mMinVisibleChange = minimumVisibleChange;
+ return this;
+ }
+
+ public SpringAnimationBuilder<T> setStartVelocity(float startVelocity) {
+ mVelocity = startVelocity;
+ return this;
+ }
+
+ @Override
+ public void setValue(T object, float time) {
+ mCurrentTime = time;
+ mProperty.setValue(
+ object, (float) (exponentialComponent(time) * cosSinX(time)) + mEndValue);
+ }
+
+ @Override
+ public Float get(T t) {
+ return mCurrentTime;
+ }
+
+ public ObjectAnimator build(Context context) {
+ int singleFrameMs = DefaultDisplay.getSingleFrameMs(context);
+ double naturalFreq = Math.sqrt(mStiffness);
+ double dampedFreq = naturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio);
+
+ // All the calculations assume the stable position to be 0, shift the values accordingly.
+ beta = 2 * mDampingRatio * naturalFreq;
+ gamma = dampedFreq;
+ a = mStartValue - mEndValue;
+ b = beta * a / (2 * gamma) + mVelocity / gamma;
+
+ va = a * beta / 2 - b * gamma;
+ vb = a * gamma + beta * b / 2;
+
+ mValueThreshold = mMinVisibleChange * THRESHOLD_MULTIPLIER;
+
+ // This multiplier is used to calculate the velocity threshold given a certain value
+ // threshold. The idea is that if it takes >= 1 frame to move the value threshold amount,
+ // then the velocity is a reasonable threshold.
+ mVelocityThreshold = mValueThreshold * 1000.0 / singleFrameMs;
+
+ // Find the duration (in seconds) for the spring to reach equilibrium.
+ // equilibrium is reached when x = 0
+ double duration = Math.atan2(-a, b) / gamma;
+
+ // Keep moving ahead until the velocity reaches equilibrium.
+ double piByG = Math.PI / gamma;
+ while (duration < 0 || Math.abs(exponentialComponent(duration) * cosSinV(duration))
+ >= mVelocityThreshold) {
+ duration += piByG;
+ }
+
+ // Find the shortest time
+ double edgeTime = Math.max(0, duration - piByG / 2);
+ double minDiff = singleFrameMs / 2000.0; // Half frame time in seconds
+
+ do {
+ if ((duration - edgeTime) < minDiff) {
+ break;
+ }
+ double mid = (edgeTime + duration) / 2;
+ if (isAtEquilibrium(mid)) {
+ duration = mid;
+ } else {
+ edgeTime = mid;
+ }
+ } while (true);
+
+
+ long durationMs = (long) (1000.0 * duration);
+ ObjectAnimator animator = ObjectAnimator.ofFloat(mTarget, this, 0, (float) duration);
+ animator.setDuration(durationMs).setInterpolator(Interpolators.LINEAR);
+ animator.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mProperty.setValue(mTarget, mEndValue);
+ }
+ });
+ return animator;
+ }
+
+ private boolean isAtEquilibrium(double t) {
+ double ec = exponentialComponent(t);
+
+ if (Math.abs(ec * cosSinX(t)) >= mValueThreshold) {
+ return false;
+ }
+ return Math.abs(ec * cosSinV(t)) < mVelocityThreshold;
+ }
+
+ private double exponentialComponent(double t) {
+ return Math.pow(Math.E, - beta * t / 2);
+ }
+
+ private double cosSinX(double t) {
+ return cosSin(t, a, b);
+ }
+
+ private double cosSinV(double t) {
+ return cosSin(t, va, vb);
+ }
+
+ private double cosSin(double t, double cosFactor, double sinFactor) {
+ double angle = t * gamma;
+ return cosFactor * Math.cos(angle) + sinFactor * Math.sin(angle);
+ }
+}
diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java
index 395fed2..91a3106 100644
--- a/src/com/android/launcher3/anim/SpringObjectAnimator.java
+++ b/src/com/android/launcher3/anim/SpringObjectAnimator.java
@@ -96,7 +96,10 @@
}
});
- mSpring.addUpdateListener((animation, value, velocity) -> mSpringEnded = false);
+ mSpring.addUpdateListener((animation, value, velocity) -> {
+ mSpringEnded = false;
+ mEnded = false;
+ });
mSpring.addEndListener((animation, canceled, value, velocity) -> {
mSpringEnded = true;
tryEnding();
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 43ae651..81c95cb 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -53,9 +53,6 @@
}
public static void sendStateEventToTest(Context context, int stateOrdinal) {
- if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
- android.util.Log.e(TestProtocol.NO_ALLAPPS_EVENT_TAG, "sendStateEventToTest");
- }
final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
if (accessibilityManager == null) return;
diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
index dfdcc70..46c9006 100644
--- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
+++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
@@ -3,7 +3,6 @@
import android.content.Context;
import android.icu.text.AlphabeticIndex;
import android.os.LocaleList;
-import android.util.Log;
import com.android.launcher3.Utilities;
@@ -12,28 +11,32 @@
import androidx.annotation.NonNull;
public class AlphabeticIndexCompat {
- private static final String TAG = "AlphabeticIndexCompat";
private static final String MID_DOT = "\u2219";
- private final BaseIndex mBaseIndex;
private final String mDefaultMiscLabel;
+ private final AlphabeticIndex.ImmutableIndex mBaseIndex;
+
public AlphabeticIndexCompat(Context context) {
- BaseIndex index = null;
+ this(context.getResources().getConfiguration().getLocales());
+ }
- try {
- index = new AlphabeticIndexVN(context);
- } catch (Exception e) {
- Log.d(TAG, "Unable to load the system index", e);
+ public AlphabeticIndexCompat(LocaleList locales) {
+ int localeCount = locales.size();
+
+ Locale primaryLocale = localeCount == 0 ? Locale.ENGLISH : locales.get(0);
+ AlphabeticIndex indexBuilder = new AlphabeticIndex(primaryLocale);
+ for (int i = 1; i < localeCount; i++) {
+ indexBuilder.addLabels(locales.get(i));
}
+ indexBuilder.addLabels(Locale.ENGLISH);
+ mBaseIndex = indexBuilder.buildImmutableIndex();
- mBaseIndex = index == null ? new BaseIndex() : index;
-
- if (context.getResources().getConfiguration().locale
- .getLanguage().equals(Locale.JAPANESE.getLanguage())) {
+ if (primaryLocale.getLanguage().equals(Locale.JAPANESE.getLanguage())) {
// Japanese character 他 ("misc")
mDefaultMiscLabel = "\u4ed6";
- // TODO(winsonc, omakoto): We need to handle Japanese sections better, especially the kanji
+ // TODO(winsonc, omakoto): We need to handle Japanese sections better,
+ // especially the kanji
} else {
// Dot
mDefaultMiscLabel = MID_DOT;
@@ -45,7 +48,7 @@
*/
public String computeSectionName(@NonNull CharSequence cs) {
String s = Utilities.trim(cs);
- String sectionName = mBaseIndex.getBucketLabel(mBaseIndex.getBucketIndex(s));
+ String sectionName = mBaseIndex.getBucket(mBaseIndex.getBucketIndex(s)).getLabel();
if (Utilities.trim(sectionName).isEmpty() && s.length() > 0) {
int c = s.codePointAt(0);
boolean startsWithDigit = Character.isDigit(c);
@@ -66,71 +69,4 @@
}
return sectionName;
}
-
- /**
- * Base class to support Alphabetic indexing if not supported by the framework.
- * TODO(winsonc): disable for non-english locales
- */
- private static class BaseIndex {
-
- private static final String BUCKETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-";
- private static final int UNKNOWN_BUCKET_INDEX = BUCKETS.length() - 1;
-
- /**
- * Returns the index of the bucket in which the given string should appear.
- */
- protected int getBucketIndex(@NonNull String s) {
- if (s.isEmpty()) {
- return UNKNOWN_BUCKET_INDEX;
- }
- int index = BUCKETS.indexOf(s.substring(0, 1).toUpperCase());
- if (index != -1) {
- return index;
- }
- return UNKNOWN_BUCKET_INDEX;
- }
-
- /**
- * Returns the label for the bucket at the given index (as returned by getBucketIndex).
- */
- protected String getBucketLabel(int index) {
- return BUCKETS.substring(index, index + 1);
- }
- }
-
- /**
- * Implementation based on {@link AlphabeticIndex}.
- */
- private static class AlphabeticIndexVN extends BaseIndex {
-
- private final AlphabeticIndex.ImmutableIndex mAlphabeticIndex;
-
- public AlphabeticIndexVN(Context context) {
- LocaleList locales = context.getResources().getConfiguration().getLocales();
- int localeCount = locales.size();
-
- Locale primaryLocale = localeCount == 0 ? Locale.ENGLISH : locales.get(0);
- AlphabeticIndex indexBuilder = new AlphabeticIndex(primaryLocale);
- for (int i = 1; i < localeCount; i++) {
- indexBuilder.addLabels(locales.get(i));
- }
- indexBuilder.addLabels(Locale.ENGLISH);
-
- mAlphabeticIndex = indexBuilder.buildImmutableIndex();
- }
-
- /**
- * Returns the index of the bucket in which {@param s} should appear.
- */
- protected int getBucketIndex(String s) {
- return mAlphabeticIndex.getBucketIndex(s);
- }
-
- /**
- * Returns the label for the bucket at the given index
- */
- protected String getBucketLabel(int index) {
- return mAlphabeticIndex.getBucket(index).getLabel();
- }
- }
}
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
index 3243256..fc5d11c 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
@@ -23,19 +23,18 @@
import android.os.Bundle;
import android.os.UserHandle;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.custom.CustomWidgetParser;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.util.HashMap;
import java.util.List;
-import androidx.annotation.Nullable;
-
public abstract class AppWidgetManagerCompat {
private static final Object sInstanceLock = new Object();
@@ -63,11 +62,9 @@
}
public LauncherAppWidgetProviderInfo getLauncherAppWidgetInfo(int appWidgetId) {
- if (FeatureFlags.ENABLE_CUSTOM_WIDGETS
- && appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
- return CustomWidgetParser.getWidgetProvider(mContext, appWidgetId);
+ if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
+ return CustomWidgetManager.INSTANCE.get(mContext).getWidgetProvider(appWidgetId);
}
-
AppWidgetProviderInfo info = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
return info == null ? null : LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
}
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
index 1065748..c8b1f67 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
@@ -24,12 +24,15 @@
import android.os.UserHandle;
import android.os.UserManager;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.custom.CustomWidgetParser;
+import com.android.launcher3.widget.custom.CustomAppWidgetProviderInfo;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.util.ArrayList;
import java.util.Collections;
@@ -37,8 +40,6 @@
import java.util.Iterator;
import java.util.List;
-import androidx.annotation.Nullable;
-
class AppWidgetManagerCompatVL extends AppWidgetManagerCompat {
private final UserManager mUserManager;
@@ -54,14 +55,11 @@
return Collections.emptyList();
}
if (packageUser == null) {
- ArrayList<AppWidgetProviderInfo> providers = new ArrayList<AppWidgetProviderInfo>();
+ ArrayList<AppWidgetProviderInfo> providers = new ArrayList<>();
for (UserHandle user : mUserManager.getUserProfiles()) {
providers.addAll(mAppWidgetManager.getInstalledProvidersForProfile(user));
}
-
- if (FeatureFlags.ENABLE_CUSTOM_WIDGETS) {
- providers.addAll(CustomWidgetParser.getCustomWidgets(mContext));
- }
+ providers.addAll(getCustomWidgets());
return providers;
}
// Only get providers for the given package/user.
@@ -74,9 +72,9 @@
}
}
- if (FeatureFlags.ENABLE_CUSTOM_WIDGETS && Process.myUserHandle().equals(packageUser.mUser)
+ if (Process.myUserHandle().equals(packageUser.mUser)
&& mContext.getPackageName().equals(packageUser.mPackageName)) {
- providers.addAll(CustomWidgetParser.getCustomWidgets(mContext));
+ providers.addAll(getCustomWidgets());
}
return providers;
}
@@ -87,9 +85,7 @@
if (FeatureFlags.GO_DISABLE_WIDGETS) {
return false;
}
-
- if (FeatureFlags.ENABLE_CUSTOM_WIDGETS
- && appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
+ if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
return true;
}
return mAppWidgetManager.bindAppWidgetIdIfAllowed(
@@ -108,9 +104,8 @@
}
}
- if (FeatureFlags.ENABLE_CUSTOM_WIDGETS && Process.myUserHandle().equals(user)) {
- for (LauncherAppWidgetProviderInfo info :
- CustomWidgetParser.getCustomWidgets(mContext)) {
+ if (Process.myUserHandle().equals(user)) {
+ for (LauncherAppWidgetProviderInfo info : getCustomWidgets()) {
if (info.provider.equals(provider)) {
return info;
}
@@ -131,13 +126,13 @@
result.put(new ComponentKey(info.provider, user), info);
}
}
-
- if (FeatureFlags.ENABLE_CUSTOM_WIDGETS) {
- for (LauncherAppWidgetProviderInfo info :
- CustomWidgetParser.getCustomWidgets(mContext)) {
- result.put(new ComponentKey(info.provider, info.getProfile()), info);
- }
+ for (LauncherAppWidgetProviderInfo info : getCustomWidgets()) {
+ result.put(new ComponentKey(info.provider, info.getProfile()), info);
}
return result;
}
+
+ List<CustomAppWidgetProviderInfo> getCustomWidgets() {
+ return CustomWidgetManager.INSTANCE.get(mContext).getCustomWidgets();
+ }
}
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
index b7b0563..11ec333 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
@@ -19,14 +19,14 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.PackageUserKey;
import java.util.Collections;
import java.util.List;
-import androidx.annotation.Nullable;
-
class AppWidgetManagerCompatVO extends AppWidgetManagerCompatVL {
AppWidgetManagerCompatVO(Context context) {
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index 58fc73d..39f6949 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -22,30 +22,34 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionCallback;
import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.UserHandle;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.PackageUserKey;
import java.util.List;
-import androidx.annotation.Nullable;
-
public abstract class LauncherAppsCompat {
public interface OnAppsChangedCallbackCompat {
- void onPackageRemoved(String packageName, UserHandle user);
- void onPackageAdded(String packageName, UserHandle user);
- void onPackageChanged(String packageName, UserHandle user);
- void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing);
- void onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing);
- void onPackagesSuspended(String[] packageNames, UserHandle user);
- void onPackagesUnsuspended(String[] packageNames, UserHandle user);
- void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts,
- UserHandle user);
+ default void onPackageRemoved(String packageName, UserHandle user) { }
+ default void onPackageAdded(String packageName, UserHandle user) { }
+ default void onPackageChanged(String packageName, UserHandle user) { }
+ default void onPackagesAvailable(String[] packageNames, UserHandle user,
+ boolean replacing) { }
+ default void onPackagesUnavailable(String[] packageNames, UserHandle user,
+ boolean replacing) { }
+ default void onPackagesSuspended(String[] packageNames, UserHandle user) { }
+ default void onPackagesUnsuspended(String[] packageNames, UserHandle user) { }
+ default void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts,
+ UserHandle user) { }
}
protected LauncherAppsCompat() {
@@ -88,4 +92,8 @@
@Nullable PackageUserKey packageUser);
public abstract List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions();
+
+ public abstract void registerSessionCallback(LooperExecutor executor,
+ SessionCallback sessionCallback);
+ public abstract void unregisterSessionCallback(SessionCallback sessionCallback);
}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
index 1d19b53..281274c 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
@@ -23,6 +23,7 @@
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionCallback;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
@@ -31,11 +32,15 @@
import android.os.Process;
import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.Log;
import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVL;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import androidx.annotation.NonNull;
@@ -167,6 +172,10 @@
@Override
public void onPackagesSuspended(String[] packageNames, UserHandle user) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.APP_NOT_DISABLED, "onPackagesSuspended: " +
+ Arrays.toString(packageNames));
+ }
mCallback.onPackagesSuspended(packageNames, user);
}
@@ -205,5 +214,17 @@
public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() {
return mContext.getPackageManager().getPackageInstaller().getAllSessions();
}
+
+ @Override
+ public void registerSessionCallback(LooperExecutor executor, SessionCallback sessionCallback) {
+ mContext.getPackageManager().getPackageInstaller().registerSessionCallback(sessionCallback,
+ executor.getHandler());
+ }
+
+ @Override
+ public void unregisterSessionCallback(SessionCallback sessionCallback) {
+ mContext.getPackageManager().getPackageInstaller()
+ .unregisterSessionCallback(sessionCallback);
+ }
}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
index 6e7a1bd..5e13d00 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
@@ -16,6 +16,8 @@
package com.android.launcher3.compat;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
@@ -30,19 +32,17 @@
import android.os.Process;
import android.os.UserHandle;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVO;
import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
import java.util.List;
-import androidx.annotation.Nullable;
-
@TargetApi(26)
public class LauncherAppsCompatVO extends LauncherAppsCompatVL {
@@ -120,7 +120,7 @@
}
} else {
// Block the worker thread until the accept() is called.
- new LooperExecutor(LauncherModel.getWorkerLooper()).execute(new Runnable() {
+ MODEL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
try {
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java b/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java
index 0a1811e..48805af 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java
@@ -18,8 +18,10 @@
import android.annotation.TargetApi;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionCallback;
+
+import com.android.launcher3.util.LooperExecutor;
import java.util.List;
@@ -33,4 +35,14 @@
public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() {
return mLauncherApps.getAllPackageInstallerSessions();
}
+
+ @Override
+ public void registerSessionCallback(LooperExecutor executor, SessionCallback sessionCallback) {
+ mLauncherApps.registerPackageInstallerSessionCallback(executor, sessionCallback);
+ }
+
+ @Override
+ public void unregisterSessionCallback(SessionCallback sessionCallback) {
+ mLauncherApps.unregisterPackageInstallerSessionCallback(sessionCallback);
+ }
}
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java
index 7dad7e9..55df98b 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompat.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java
@@ -19,14 +19,25 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.os.Process;
+import android.os.UserHandle;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import androidx.annotation.NonNull;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.PackageUserKey;
+
public abstract class PackageInstallerCompat {
+ // Set<String> of session ids of promise icons that have been added to the home screen
+ // as FLAG_PROMISE_NEW_INSTALLS.
+ protected static final String PROMISE_ICON_IDS = "promise_icon_ids";
+
public static final int STATUS_INSTALLED = 0;
public static final int STATUS_INSTALLING = 1;
public static final int STATUS_FAILED = 2;
@@ -43,10 +54,19 @@
}
}
+ public static UserHandle getUserHandle(SessionInfo info) {
+ return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle();
+ }
+
/**
* @return a map of active installs to their progress
*/
- public abstract HashMap<String, PackageInstaller.SessionInfo> updateAndGetActiveSessionCache();
+ public abstract HashMap<PackageUserKey, SessionInfo> updateAndGetActiveSessionCache();
+
+ /**
+ * @return an active SessionInfo for {@param pkg} or null if none exists.
+ */
+ public abstract SessionInfo getActiveSessionInfo(UserHandle user, String pkg);
public abstract void onStop();
@@ -55,30 +75,44 @@
public final String packageName;
public final int state;
public final int progress;
+ public final UserHandle user;
- private PackageInstallInfo(@NonNull PackageInstaller.SessionInfo info) {
+ private PackageInstallInfo(@NonNull SessionInfo info) {
this.state = STATUS_INSTALLING;
this.packageName = info.getAppPackageName();
this.componentName = new ComponentName(packageName, "");
this.progress = (int) (info.getProgress() * 100f);
+ this.user = getUserHandle(info);
}
- public PackageInstallInfo(String packageName, int state, int progress) {
+ public PackageInstallInfo(String packageName, int state, int progress, UserHandle user) {
this.state = state;
this.packageName = packageName;
this.componentName = new ComponentName(packageName, "");
this.progress = progress;
+ this.user = user;
}
- public static PackageInstallInfo fromInstallingState(PackageInstaller.SessionInfo info) {
+ public static PackageInstallInfo fromInstallingState(SessionInfo info) {
return new PackageInstallInfo(info);
}
- public static PackageInstallInfo fromState(int state, String packageName) {
- return new PackageInstallInfo(packageName, state, 0 /* progress */);
+ public static PackageInstallInfo fromState(int state, String packageName, UserHandle user) {
+ return new PackageInstallInfo(packageName, state, 0 /* progress */, user);
}
}
- public abstract List<PackageInstaller.SessionInfo> getAllVerifiedSessions();
+ public abstract List<SessionInfo> getAllVerifiedSessions();
+
+ /**
+ * Returns true if a promise icon was already added to the home screen for {@param sessionId}.
+ * Applicable only for icons with flag FLAG_PROMISE_NEW_INSTALLS.
+ */
+ public abstract boolean promiseIconAddedForId(int sessionId);
+
+ /**
+ * Applicable only for icons with flag FLAG_PROMISE_NEW_INSTALLS.
+ */
+ public abstract void removePromiseIconId(int sessionId);
}
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
index a34ca50..409b21d 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
@@ -16,22 +16,26 @@
package com.android.launcher3.compat;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionCallback;
import android.content.pm.PackageInstaller.SessionInfo;
-import android.os.Handler;
-import android.os.Process;
+import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.SparseArray;
+import com.android.launcher3.SessionCommitReceiver;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
@@ -39,42 +43,78 @@
import java.util.Iterator;
import java.util.List;
+import static com.android.launcher3.Utilities.getPrefs;
+
public class PackageInstallerCompatVL extends PackageInstallerCompat {
private static final boolean DEBUG = false;
- @Thunk final SparseArray<String> mActiveSessions = new SparseArray<>();
+ @Thunk final SparseArray<PackageUserKey> mActiveSessions = new SparseArray<>();
@Thunk final PackageInstaller mInstaller;
private final IconCache mCache;
- private final Handler mWorker;
private final Context mAppContext;
private final HashMap<String,Boolean> mSessionVerifiedMap = new HashMap<>();
private final LauncherAppsCompat mLauncherApps;
+ private final IntSet mPromiseIconIds;
PackageInstallerCompatVL(Context context) {
mAppContext = context.getApplicationContext();
mInstaller = context.getPackageManager().getPackageInstaller();
mCache = LauncherAppState.getInstance(context).getIconCache();
- mWorker = new Handler(LauncherModel.getWorkerLooper());
- mInstaller.registerSessionCallback(mCallback, mWorker);
mLauncherApps = LauncherAppsCompat.getInstance(context);
+ mLauncherApps.registerSessionCallback(MODEL_EXECUTOR, mCallback);
+ mPromiseIconIds = IntSet.wrap(IntArray.fromConcatString(
+ getPrefs(context).getString(PROMISE_ICON_IDS, "")));
+
+ cleanUpPromiseIconIds();
+ }
+
+ private void cleanUpPromiseIconIds() {
+ IntArray existingIds = new IntArray();
+ for (SessionInfo info : updateAndGetActiveSessionCache().values()) {
+ existingIds.add(info.getSessionId());
+ }
+ IntArray idsToRemove = new IntArray();
+
+ for (int i = mPromiseIconIds.size() - 1; i >= 0; --i) {
+ if (!existingIds.contains(mPromiseIconIds.getArray().get(i))) {
+ idsToRemove.add(mPromiseIconIds.getArray().get(i));
+ }
+ }
+ for (int i = idsToRemove.size() - 1; i >= 0; --i) {
+ mPromiseIconIds.getArray().removeValue(idsToRemove.get(i));
+ }
}
@Override
- public HashMap<String, SessionInfo> updateAndGetActiveSessionCache() {
- HashMap<String, SessionInfo> activePackages = new HashMap<>();
- UserHandle user = Process.myUserHandle();
+ public HashMap<PackageUserKey, SessionInfo> updateAndGetActiveSessionCache() {
+ HashMap<PackageUserKey, SessionInfo> activePackages = new HashMap<>();
for (SessionInfo info : getAllVerifiedSessions()) {
- addSessionInfoToCache(info, user);
+ addSessionInfoToCache(info, getUserHandle(info));
if (info.getAppPackageName() != null) {
- activePackages.put(info.getAppPackageName(), info);
- mActiveSessions.put(info.getSessionId(), info.getAppPackageName());
+ activePackages.put(new PackageUserKey(info.getAppPackageName(),
+ getUserHandle(info)), info);
+ mActiveSessions.put(info.getSessionId(),
+ new PackageUserKey(info.getAppPackageName(), getUserHandle(info)));
}
}
return activePackages;
}
+ public SessionInfo getActiveSessionInfo(UserHandle user, String pkg) {
+ for (SessionInfo info : getAllVerifiedSessions()) {
+ boolean match = pkg.equals(info.getAppPackageName());
+ if (Utilities.ATLEAST_Q && !user.equals(getUserHandle(info))) {
+ match = false;
+ }
+ if (match) {
+ return info;
+ }
+ }
+ return null;
+ }
+
@Thunk void addSessionInfoToCache(SessionInfo info, UserHandle user) {
String packageName = info.getAppPackageName();
if (packageName != null) {
@@ -85,7 +125,7 @@
@Override
public void onStop() {
- mInstaller.unregisterSessionCallback(mCallback);
+ mLauncherApps.unregisterSessionCallback(mCallback);
}
@Thunk void sendUpdate(PackageInstallInfo info) {
@@ -95,6 +135,30 @@
}
}
+ /**
+ * Add a promise app icon to the workspace iff:
+ * - The settings for it are enabled
+ * - The user installed the app
+ * - There is an app icon and label (For apps with no launching activity, no icon is provided).
+ * - The app is not already installed
+ * - A promise icon for the session has not already been created
+ */
+ private void tryQueuePromiseAppIcon(SessionInfo sessionInfo) {
+ if (Utilities.ATLEAST_OREO && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
+ && SessionCommitReceiver.isEnabled(mAppContext)
+ && verify(sessionInfo) != null
+ && sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER
+ && sessionInfo.getAppIcon() != null
+ && !TextUtils.isEmpty(sessionInfo.getAppLabel())
+ && !mPromiseIconIds.contains(sessionInfo.getSessionId())
+ && mLauncherApps.getApplicationInfo(sessionInfo.getAppPackageName(), 0,
+ getUserHandle(sessionInfo)) == null) {
+ SessionCommitReceiver.queuePromiseAppIconAddition(mAppContext, sessionInfo);
+ mPromiseIconIds.add(sessionInfo.getSessionId());
+ updatePromiseIconPrefs();
+ }
+ }
+
private final SessionCallback mCallback = new SessionCallback() {
@Override
@@ -107,19 +171,31 @@
PackageInstallInfo.fromInstallingState(sessionInfo));
}
}
+
+ tryQueuePromiseAppIcon(sessionInfo);
}
@Override
public void onFinished(int sessionId, boolean success) {
// For a finished session, we can't get the session info. So use the
// packageName from our local cache.
- String packageName = mActiveSessions.get(sessionId);
+ PackageUserKey key = mActiveSessions.get(sessionId);
mActiveSessions.remove(sessionId);
- if (packageName != null) {
- sendUpdate(PackageInstallInfo.fromState(
- success ? STATUS_INSTALLED : STATUS_FAILED,
- packageName));
+ if (key != null && key.mPackageName != null) {
+ String packageName = key.mPackageName;
+ sendUpdate(PackageInstallInfo.fromState(success ? STATUS_INSTALLED : STATUS_FAILED,
+ packageName, key.mUser));
+
+ if (!success && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
+ && mPromiseIconIds.contains(sessionId)) {
+ LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+ if (appState != null) {
+ appState.getModel().onSessionFailure(packageName, key.mUser);
+ }
+ // If it is successful, the id is removed in the the package added flow.
+ removePromiseIconId(sessionId);
+ }
}
}
@@ -136,17 +212,23 @@
@Override
public void onBadgingChanged(int sessionId) {
- pushSessionDisplayToLauncher(sessionId);
+ SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId);
+ if (sessionInfo != null) {
+ tryQueuePromiseAppIcon(sessionInfo);
+ }
}
private SessionInfo pushSessionDisplayToLauncher(int sessionId) {
SessionInfo session = verify(mInstaller.getSessionInfo(sessionId));
if (session != null && session.getAppPackageName() != null) {
- mActiveSessions.put(sessionId, session.getAppPackageName());
- addSessionInfoToCache(session, Process.myUserHandle());
+ UserHandle user = getUserHandle(session);
+ mActiveSessions.put(session.getSessionId(),
+ new PackageUserKey(session.getAppPackageName(), user));
+ addSessionInfoToCache(session, user);
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app != null) {
- app.getModel().updateSessionDisplayInfo(session.getAppPackageName());
+ app.getModel().updateSessionDisplayInfo(session.getAppPackageName(),
+ user);
}
return session;
}
@@ -165,7 +247,7 @@
if (!mSessionVerifiedMap.containsKey(pkg)) {
LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mAppContext);
boolean hasSystemFlag = launcherApps.getApplicationInfo(pkg,
- ApplicationInfo.FLAG_SYSTEM, Process.myUserHandle()) != null;
+ ApplicationInfo.FLAG_SYSTEM, getUserHandle(sessionInfo)) != null;
mSessionVerifiedMap.put(pkg, DEBUG || hasSystemFlag);
}
}
@@ -185,4 +267,23 @@
}
return list;
}
+
+ @Override
+ public boolean promiseIconAddedForId(int sessionId) {
+ return mPromiseIconIds.contains(sessionId);
+ }
+
+ @Override
+ public void removePromiseIconId(int sessionId) {
+ if (mPromiseIconIds.contains(sessionId)) {
+ mPromiseIconIds.getArray().removeValue(sessionId);
+ updatePromiseIconPrefs();
+ }
+ }
+
+ private void updatePromiseIconPrefs() {
+ getPrefs(mAppContext).edit()
+ .putString(PROMISE_ICON_IDS, mPromiseIconIds.getArray().toConcatString())
+ .apply();
+ }
}
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 45639e0..4b90e42 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -18,19 +18,15 @@
import static androidx.core.util.Preconditions.checkNotNull;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
import androidx.annotation.GuardedBy;
import androidx.annotation.Keep;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.Utilities;
+import com.android.launcher3.uioverrides.TogglableFlag;
import java.util.ArrayList;
import java.util.List;
@@ -41,11 +37,9 @@
* Defines a set of flags used to control various launcher behaviors.
*
* <p>All the flags should be defined here with appropriate default values.
- *
- * <p>This class is kept package-private to prevent direct access.
*/
@Keep
-abstract class BaseFlags {
+public abstract class BaseFlags {
private static final Object sLock = new Object();
@GuardedBy("sLock")
@@ -66,6 +60,11 @@
// When enabled the promise icon is visible in all apps while installation an app.
public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false;
+ // When enabled a promise icon is added to the home screen when install session is active.
+ public static final TogglableFlag PROMISE_APPS_NEW_INSTALLS =
+ new TogglableFlag("PROMISE_APPS_NEW_INSTALLS", true,
+ "Adds a promise icon to the home screen for new install sessions.");
+
// Enable moving the QSB on the 0th screen of the workspace
public static final boolean QSB_ON_FIRST_SCREEN = true;
@@ -75,9 +74,6 @@
//Feature flag to enable pulling down navigation shade from workspace.
public static final boolean PULL_DOWN_STATUS_BAR = true;
- // When true, custom widgets are loaded using CustomWidgetParser.
- public static final boolean ENABLE_CUSTOM_WIDGETS = false;
-
// Features to control Launcher3Go behavior
public static final boolean GO_DISABLE_WIDGETS = false;
@@ -105,22 +101,38 @@
"ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview");
public static final TogglableFlag ENABLE_HINTS_IN_OVERVIEW = new TogglableFlag(
- "ENABLE_HINTS_IN_OVERVIEW", false,
+ "ENABLE_HINTS_IN_OVERVIEW", true,
"Show chip hints and gleams on the overview screen");
public static final TogglableFlag FAKE_LANDSCAPE_UI = new TogglableFlag(
"FAKE_LANDSCAPE_UI", false,
"Rotate launcher UI instead of using transposed layout");
+ public static final TogglableFlag FOLDER_NAME_SUGGEST = new TogglableFlag(
+ "FOLDER_NAME_SUGGEST", true,
+ "Suggests folder names instead of blank text.");
+
+ public static final TogglableFlag APP_SEARCH_IMPROVEMENTS = new TogglableFlag(
+ "APP_SEARCH_IMPROVEMENTS", true,
+ "Adds localized title and keyword search and ranking");
+
+ public static final TogglableFlag ENABLE_PREDICTION_DISMISS = new TogglableFlag(
+ "ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list");
+
+ public static final TogglableFlag ASSISTANT_GIVES_LAUNCHER_FOCUS = new TogglableFlag(
+ "ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
+ "Allow Launcher to handle nav bar gestures while Assistant is running over it");
+
public static void initialize(Context context) {
// Avoid the disk read for user builds
if (Utilities.IS_DEBUG_DEVICE) {
synchronized (sLock) {
- for (TogglableFlag flag : sFlags) {
+ for (BaseTogglableFlag flag : sFlags) {
flag.initialize(context);
}
}
}
+ APP_SEARCH_IMPROVEMENTS.initialize(context);
}
static List<TogglableFlag> getTogglableFlags() {
@@ -132,27 +144,30 @@
SortedMap<String, TogglableFlag> flagsByKey = new TreeMap<>();
synchronized (sLock) {
for (TogglableFlag flag : sFlags) {
- flagsByKey.put(flag.key, flag);
+ flagsByKey.put(((BaseTogglableFlag) flag).getKey(), flag);
}
}
return new ArrayList<>(flagsByKey.values());
}
- public static class TogglableFlag {
+ public static abstract class BaseTogglableFlag {
private final String key;
+ // should be value that is hardcoded in client side.
+ // Comparatively, getDefaultValue() can be overridden.
private final boolean defaultValue;
private final String description;
private boolean currentValue;
- TogglableFlag(
+ public BaseTogglableFlag(
String key,
boolean defaultValue,
String description) {
this.key = checkNotNull(key);
this.currentValue = this.defaultValue = defaultValue;
this.description = checkNotNull(description);
+
synchronized (sLock) {
- sFlags.add(this);
+ sFlags.add((TogglableFlag)this);
}
}
@@ -162,18 +177,22 @@
currentValue = value;
}
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public String getKey() {
return key;
}
- void initialize(Context context) {
- currentValue = getFromStorage(context, defaultValue);
+
+ protected void initialize(Context context) {
+ currentValue = getFromStorage(context, getDefaultValue());
}
+ protected abstract boolean getOverridenDefaultValue(boolean value);
+
+ protected abstract void addChangeListener(Context context, Runnable r);
+
public void updateStorage(Context context, boolean value) {
SharedPreferences.Editor editor = context.getSharedPreferences(FLAGS_PREF_NAME,
Context.MODE_PRIVATE).edit();
- if (value == defaultValue) {
+ if (value == getDefaultValue()) {
editor.remove(key).apply();
} else {
editor.putBoolean(key, value).apply();
@@ -182,11 +201,11 @@
boolean getFromStorage(Context context, boolean defaultValue) {
return context.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE)
- .getBoolean(key, defaultValue);
+ .getBoolean(key, getDefaultValue());
}
boolean getDefaultValue() {
- return defaultValue;
+ return getOverridenDefaultValue(defaultValue);
}
/** Returns the value of the flag at process start, including any overrides present. */
@@ -203,6 +222,8 @@
return "TogglableFlag{"
+ "key=" + key + ", "
+ "defaultValue=" + defaultValue + ", "
+ + "overriddenDefaultValue=" + getOverridenDefaultValue(defaultValue) + ", "
+ + "currentValue=" + currentValue + ", "
+ "description=" + description
+ "}";
}
@@ -213,9 +234,9 @@
return true;
}
if (o instanceof TogglableFlag) {
- TogglableFlag that = (TogglableFlag) o;
+ BaseTogglableFlag that = (BaseTogglableFlag) o;
return (this.key.equals(that.getKey()))
- && (this.defaultValue == that.getDefaultValue())
+ && (this.getDefaultValue() == that.getDefaultValue())
&& (this.description.equals(that.getDescription()));
}
return false;
@@ -227,54 +248,10 @@
h$ *= 1000003;
h$ ^= key.hashCode();
h$ *= 1000003;
- h$ ^= defaultValue ? 1231 : 1237;
+ h$ ^= getDefaultValue() ? 1231 : 1237;
h$ *= 1000003;
h$ ^= description.hashCode();
return h$;
}
}
-
- /**
- * Stores the FeatureFlag's value in Settings.Global instead of our SharedPrefs.
- * This is useful if we want to be able to control this flag from another process.
- */
- public static final class ToggleableGlobalSettingsFlag extends TogglableFlag {
- private ContentResolver contentResolver;
-
- ToggleableGlobalSettingsFlag(String key, boolean defaultValue, String description) {
- super(key, defaultValue, description);
- }
-
- @Override
- public void initialize(Context context) {
- contentResolver = context.getContentResolver();
- contentResolver.registerContentObserver(Settings.Global.getUriFor(getKey()), true,
- new ContentObserver(new Handler(Looper.getMainLooper())) {
- @Override
- public void onChange(boolean selfChange) {
- superInitialize(context);
- }});
- superInitialize(context);
- }
-
- private void superInitialize(Context context) {
- super.initialize(context);
- }
-
- @Override
- public void updateStorage(Context context, boolean value) {
- if (contentResolver == null) {
- return;
- }
- Settings.Global.putInt(contentResolver, getKey(), value ? 1 : 0);
- }
-
- @Override
- boolean getFromStorage(Context context, boolean defaultValue) {
- if (contentResolver == null) {
- return defaultValue;
- }
- return Settings.Global.getInt(contentResolver, getKey(), defaultValue ? 1 : 0) == 1;
- }
- }
}
diff --git a/src/com/android/launcher3/config/FlagTogglerPrefUi.java b/src/com/android/launcher3/config/FlagTogglerPrefUi.java
index 5ecb186..54e5322 100644
--- a/src/com/android/launcher3/config/FlagTogglerPrefUi.java
+++ b/src/com/android/launcher3/config/FlagTogglerPrefUi.java
@@ -26,12 +26,13 @@
import android.widget.Toast;
import com.android.launcher3.R;
-import com.android.launcher3.config.BaseFlags.TogglableFlag;
import androidx.preference.PreferenceDataStore;
import androidx.preference.PreferenceFragment;
import androidx.preference.PreferenceGroup;
import androidx.preference.SwitchPreference;
+import com.android.launcher3.config.BaseFlags.BaseTogglableFlag;
+import com.android.launcher3.uioverrides.TogglableFlag;
/**
* Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}.
@@ -62,7 +63,7 @@
@Override
public boolean getBoolean(String key, boolean defaultValue) {
- for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
+ for (BaseTogglableFlag flag : FeatureFlags.getTogglableFlags()) {
if (flag.getKey().equals(key)) {
return flag.getFromStorage(mContext, defaultValue);
}
@@ -83,7 +84,7 @@
// flag with a different value than the default. That way, when we flip flags in
// future, engineers will pick up the new value immediately. To accomplish this, we use a
// custom preference data store.
- for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
+ for (BaseTogglableFlag flag : FeatureFlags.getTogglableFlags()) {
SwitchPreference switchPreference = new SwitchPreference(mContext);
switchPreference.setKey(flag.getKey());
switchPreference.setDefaultValue(flag.getDefaultValue());
@@ -99,7 +100,7 @@
/**
* Updates the summary to show the description and whether the flag overrides the default value.
*/
- private void updateSummary(SwitchPreference switchPreference, TogglableFlag flag) {
+ private void updateSummary(SwitchPreference switchPreference, BaseTogglableFlag flag) {
String onWarning = flag.getDefaultValue() ? "" : "<b>OVERRIDDEN</b><br>";
String offWarning = flag.getDefaultValue() ? "<b>OVERRIDDEN</b><br>" : "";
switchPreference.setSummaryOn(Html.fromHtml(onWarning + flag.getDescription()));
@@ -134,7 +135,7 @@
}
}
- private boolean getFlagStateFromSharedPrefs(TogglableFlag flag) {
+ private boolean getFlagStateFromSharedPrefs(BaseTogglableFlag flag) {
return mDataStore.getBoolean(flag.getKey(), flag.getDefaultValue());
}
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index a72089d..9fb1090 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.annotation.TargetApi;
import android.app.ActivityOptions;
@@ -47,7 +48,6 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetHost;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.LauncherModel;
import com.android.launcher3.R;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.LauncherAppsCompatVO;
@@ -55,7 +55,6 @@
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.InstantAppResolver;
-import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.views.BaseDragLayer;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -153,16 +152,6 @@
PinItemDragListener listener = new PinItemDragListener(mRequest, bounds,
img.getBitmap().getWidth(), img.getWidth());
- Intent homeIntent = listener.addToIntent(
- new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME)
- .setPackage(getPackageName())
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-
- listener.initWhenReady();
- startActivity(homeIntent,
- ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle());
- mFinishOnPause = true;
// Start a system drag and drop. We use a transparent bitmap as preview for system drag
// as the preview is handled internally by launcher.
@@ -179,6 +168,18 @@
outShadowTouchPoint.set(SHADOW_SIZE / 2, SHADOW_SIZE / 2);
}
}, null, View.DRAG_FLAG_GLOBAL);
+
+
+ Intent homeIntent = listener.addToIntent(
+ new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setPackage(getPackageName())
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+
+ listener.initWhenReady();
+ startActivity(homeIntent,
+ ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle());
+ mFinishOnPause = true;
return false;
}
@@ -232,7 +233,7 @@
mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache());
mWidgetCell.ensurePreview();
}
- }.executeOnExecutor(new LooperExecutor(LauncherModel.getWorkerLooper()));
+ }.executeOnExecutor(MODEL_EXECUTOR);
// TODO: Create a worker looper executor and reuse that everywhere.
}
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index c719c1c..1b08723 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -137,9 +137,6 @@
@Override
public boolean shouldStartDrag(double distanceDragged) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_DRAG_TAG, "BIDL.shouldStartDrag");
- }
// Stay in pre-drag mode, if workspace is locked.
return !mLauncher.isWorkspaceLocked();
}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 72a1abb..dcdf5d6 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.Utilities.ATLEAST_Q;
import android.animation.ValueAnimator;
import android.content.ComponentName;
@@ -56,6 +57,12 @@
public class DragController implements DragDriver.EventListener, TouchController {
private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
+ /**
+ * When a drag is started from a deep press, you need to drag this much farther than normal to
+ * end a pre-drag. See {@link DragOptions.PreDragCondition#shouldStartDrag(double)}.
+ */
+ private static final int DEEP_PRESS_DISTANCE_FACTOR = 3;
+
@Thunk Launcher mLauncher;
private FlingToDeleteHelper mFlingToDeleteHelper;
@@ -91,9 +98,10 @@
private DropTarget mLastDropTarget;
- @Thunk int mLastTouch[] = new int[2];
- @Thunk long mLastTouchUpTime = -1;
- @Thunk int mDistanceSinceScroll = 0;
+ private final int[] mLastTouch = new int[2];
+ private long mLastTouchUpTime = -1;
+ private int mLastTouchClassification;
+ private int mDistanceSinceScroll = 0;
private int mTmpPoint[] = new int[2];
private Rect mDragLayerRect = new Rect();
@@ -204,7 +212,7 @@
}
mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- dragView.show(mMotionDownX, mMotionDownY);
+ dragView.show(mLastTouch[0], mLastTouch[1]);
mDistanceSinceScroll = 0;
if (!mIsInPreDrag) {
@@ -213,9 +221,7 @@
mOptions.preDragCondition.onPreDragStart(mDragObject);
}
- mLastTouch[0] = mMotionDownX;
- mLastTouch[1] = mMotionDownY;
- handleMoveEvent(mMotionDownX, mMotionDownY);
+ handleMoveEvent(mLastTouch[0], mLastTouch[1]);
mLauncher.getUserEventDispatcher().resetActionDurationMillis();
return dragView;
}
@@ -430,6 +436,11 @@
final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
final int dragLayerX = dragLayerPos[0];
final int dragLayerY = dragLayerPos[1];
+ mLastTouch[0] = dragLayerX;
+ mLastTouch[1] = dragLayerY;
+ if (ATLEAST_Q) {
+ mLastTouchClassification = ev.getClassification();
+ }
switch (action) {
case MotionEvent.ACTION_DOWN:
@@ -474,10 +485,6 @@
}
private void handleMoveEvent(int x, int y) {
- if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_DRAG_TAG,
- "handleMoveEvent 1");
- }
mDragObject.dragView.move(x, y);
// Drop on someone?
@@ -492,22 +499,12 @@
mLastTouch[0] = x;
mLastTouch[1] = y;
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_DRAG_TAG,
- "handleMoveEvent Conditions " +
- mIsInPreDrag + ", " +
- (mIsInPreDrag && mOptions.preDragCondition != null) + ", " +
- (mIsInPreDrag && mOptions.preDragCondition != null
- && mOptions.preDragCondition.shouldStartDrag(
- mDistanceSinceScroll)));
+ int distanceDragged = mDistanceSinceScroll;
+ if (ATLEAST_Q && mLastTouchClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS) {
+ distanceDragged /= DEEP_PRESS_DISTANCE_FACTOR;
}
-
if (mIsInPreDrag && mOptions.preDragCondition != null
- && mOptions.preDragCondition.shouldStartDrag(mDistanceSinceScroll)) {
- if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_DRAG_TAG,
- "handleMoveEvent 2");
- }
+ && mOptions.preDragCondition.shouldStartDrag(distanceDragged)) {
callOnDragStart();
}
}
@@ -545,10 +542,6 @@
* Call this from a drag source view.
*/
public boolean onControllerTouchEvent(MotionEvent ev) {
- if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_DRAG_TAG,
- "onControllerTouchEvent");
- }
if (mDragDriver == null || mOptions == null || mOptions.isAccessibleDrag) {
return false;
}
@@ -601,6 +594,9 @@
}
private void drop(DropTarget dropTarget, Runnable flingAnimation) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "DragController.drop");
+ }
final int[] coordinates = mCoordinatesTemp;
mDragObject.x = coordinates[0];
mDragObject.y = coordinates[1];
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index 84fc94d..01e0f92 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -17,10 +17,12 @@
package com.android.launcher3.dragndrop;
import android.content.Context;
+import android.util.Log;
import android.view.DragEvent;
import android.view.MotionEvent;
import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.testing.TestProtocol;
/**
* Base class for driving a drag/drop operation.
@@ -52,10 +54,16 @@
mEventListener.onDriverDragMove(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_UP:
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "DragDriver.ACTION_UP");
+ }
mEventListener.onDriverDragMove(ev.getX(), ev.getY());
mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
break;
case MotionEvent.ACTION_CANCEL:
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "DragDriver.ACTION_CANCEL");
+ }
mEventListener.onDriverDragCancel();
break;
}
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 6ba015b..0b6d8fb 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -48,12 +48,13 @@
import com.android.launcher3.DropTargetBar;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Workspace;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.uioverrides.UiFactory;
@@ -92,7 +93,8 @@
// Related to adjacent page hints
private final ViewGroupFocusHelper mFocusIndicatorHelper;
- private final WorkspaceAndHotseatScrim mScrim;
+ private final WorkspaceAndHotseatScrim mWorkspaceScrim;
+ private final OverviewScrim mOverviewScrim;
/**
* Used to create a new DragLayer from XML.
@@ -108,12 +110,13 @@
setChildrenDrawingOrderEnabled(true);
mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
- mScrim = new WorkspaceAndHotseatScrim(this);
+ mWorkspaceScrim = new WorkspaceAndHotseatScrim(this);
+ mOverviewScrim = new OverviewScrim(this);
}
public void setup(DragController dragController, Workspace workspace) {
mDragController = dragController;
- mScrim.setWorkspace(workspace);
+ mWorkspaceScrim.setWorkspace(workspace);
recreateControllers();
}
@@ -281,7 +284,8 @@
// The child may be scaled (always about the center of the view) so to account for it,
// we have to offset the position by the scaled size. Once we do that, we can center
// the drag view about the scaled child view.
- toY += Math.round(toScale * tv.getPaddingTop());
+ // padding will remain constant (does not scale with size)
+ toY += tv.getPaddingTop();
toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2;
if (dragView.getDragVisualizeOffset() != null) {
toY -= Math.round(toScale * dragView.getDragVisualizeOffset().y);
@@ -529,25 +533,39 @@
@Override
protected void dispatchDraw(Canvas canvas) {
// Draw the background below children.
- mScrim.draw(canvas);
+ mWorkspaceScrim.draw(canvas);
+ mOverviewScrim.updateCurrentScrimmedView(this);
mFocusIndicatorHelper.draw(canvas);
super.dispatchDraw(canvas);
}
@Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ if (child == mOverviewScrim.getScrimmedView()) {
+ mOverviewScrim.draw(canvas);
+ }
+ return super.drawChild(canvas, child, drawingTime);
+ }
+
+ @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
- mScrim.setSize(w, h);
+ mWorkspaceScrim.setSize(w, h);
}
@Override
public void setInsets(Rect insets) {
super.setInsets(insets);
- mScrim.onInsetsChanged(insets);
+ mWorkspaceScrim.onInsetsChanged(insets, mAllowSysuiScrims);
+ mOverviewScrim.onInsetsChanged(insets);
}
public WorkspaceAndHotseatScrim getScrim() {
- return mScrim;
+ return mWorkspaceScrim;
+ }
+
+ public OverviewScrim getOverviewScrim() {
+ return mOverviewScrim;
}
@Override
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 09c5e5b..f66d07e 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -17,6 +17,7 @@
package com.android.launcher3.dragndrop;
import static com.android.launcher3.Utilities.getBadge;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -41,16 +42,19 @@
import android.os.Looper;
import android.view.View;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
import com.android.launcher3.FastBitmapDrawable;
+import com.android.launcher3.FirstFrameAnimatorHelper;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.FirstFrameAnimatorHelper;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.util.Themes;
@@ -58,10 +62,6 @@
import java.util.Arrays;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
public class DragView extends View implements LauncherStateManager.StateListener {
private static final ColorMatrix sTempMatrix1 = new ColorMatrix();
private static final ColorMatrix sTempMatrix2 = new ColorMatrix();
@@ -210,7 +210,7 @@
return;
}
// Load the adaptive icon on a background thread and add the view in ui thread.
- new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(new Runnable() {
+ MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(new Runnable() {
@Override
public void run() {
Object[] outObj = new Object[1];
diff --git a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
index 589ad25..06b5c40 100644
--- a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
+++ b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
@@ -106,6 +106,7 @@
* @return the vector at which the item was flung, or null if no fling was detected.
*/
private PointF isFlingingToDelete() {
+ if (mVelocityTracker == null) return null;
if (mDropTarget == null) {
mDropTarget = (ButtonDropTarget) mLauncher.findViewById(R.id.delete_target_text);
}
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index 0c5a1fc..0bb3fba 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -16,6 +16,8 @@
package com.android.launcher3.dragndrop;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -28,8 +30,9 @@
import android.os.Build;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.Launcher;
-import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.folder.PreviewBackground;
@@ -66,20 +69,24 @@
return mBadge;
}
- public static FolderAdaptiveIcon createFolderAdaptiveIcon(
+ public static @Nullable FolderAdaptiveIcon createFolderAdaptiveIcon(
Launcher launcher, int folderId, Point dragViewSize) {
Preconditions.assertNonUiThread();
int margin = launcher.getResources()
.getDimensionPixelSize(R.dimen.blur_size_medium_outline);
// Allocate various bitmaps on the background thread, because why not!
- final Bitmap badge = Bitmap.createBitmap(
- dragViewSize.x - margin, dragViewSize.y - margin, Bitmap.Config.ARGB_8888);
+ int width = dragViewSize.x - margin;
+ int height = dragViewSize.y - margin;
+ if (width <= 0 || height <= 0) {
+ return null;
+ }
+ final Bitmap badge = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
// Create the actual drawable on the UI thread to avoid race conditions with
// FolderIcon draw pass
try {
- return new MainThreadExecutor().submit(() -> {
+ return MAIN_EXECUTOR.submit(() -> {
FolderIcon icon = launcher.findFolderIcon(folderId);
return icon == null ? null : createDrawableOnUiThread(icon, badge, dragViewSize);
}).get();
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 2ef6d70..8df5e7d 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -17,6 +17,8 @@
package com.android.launcher3.folder;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
@@ -26,12 +28,12 @@
import android.annotation.SuppressLint;
import android.appwidget.AppWidgetHostView;
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;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
@@ -74,7 +76,11 @@
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.logging.LoggerUtils;
import com.android.launcher3.pageindicators.PageIndicatorDots;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.ClipPathView;
@@ -126,9 +132,6 @@
private static final Rect sTempRect = new Rect();
private static final int MIN_FOLDERS_FOR_HARDWARE_OPTIMIZATION = 10;
- private static String sDefaultFolderName;
- private static String sHintText;
-
private final Alarm mReorderAlarm = new Alarm();
private final Alarm mOnExitAlarm = new Alarm();
private final Alarm mOnScrollHintAlarm = new Alarm();
@@ -148,7 +151,7 @@
public ExtendedEditText mFolderName;
private PageIndicatorDots mPageIndicator;
- private View mFooter;
+ protected View mFooter;
private int mFooterHeight;
// Cell ranks used for drag and drop
@@ -173,8 +176,6 @@
private boolean mDeleteFolderOnDropCompleted = false;
private boolean mSuppressFolderDeletion = false;
private boolean mItemAddedBackToSelfViaIcon = false;
- @Thunk float mFolderIconPivotX;
- @Thunk float mFolderIconPivotY;
private boolean mIsEditingName = false;
@ViewDebug.ExportedProperty(category = "launcher")
@@ -196,8 +197,6 @@
super(context, attrs);
setAlwaysDrawnWithCacheEnabled(false);
- setLocaleDependentFields(getResources(), false /* force */);
-
mLauncher = Launcher.getLauncher(context);
// We need this view to be focusable in touch mode so that when text editing of the folder
// name is complete, we have something to focus on, thus hiding the cursor and giving
@@ -315,10 +314,15 @@
// Convert to a string here to ensure that no other state associated with the text field
// gets saved.
String newTitle = mFolderName.getText().toString();
- mInfo.setTitle(newTitle);
+ mInfo.title = newTitle;
+ mFolderIcon.onTitleChanged(newTitle);
mLauncher.getModelWriter().updateItemInDatabase(mInfo);
- mFolderName.setHint(sDefaultFolderName.contentEquals(newTitle) ? sHintText : null);
+ if (TextUtils.isEmpty(mInfo.title)) {
+ mFolderName.setHint(R.string.folder_hint_text);
+ } else {
+ mFolderName.setHint(null);
+ }
sendCustomAccessibilityEvent(
this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
@@ -385,7 +389,7 @@
mInfo = info;
ArrayList<WorkspaceItemInfo> children = info.contents;
Collections.sort(children, ITEM_POS_COMPARATOR);
- mContent.bindItems(children);
+ updateItemLocationsInDatabaseBatch();
DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
if (lp == null) {
@@ -393,30 +397,40 @@
lp.customPosition = true;
setLayoutParams(lp);
}
- centerAboutIcon();
-
mItemsInvalidated = true;
- updateTextViewFocus();
mInfo.addListener(this);
- if (!sDefaultFolderName.contentEquals(mInfo.title)) {
+ if (!TextUtils.isEmpty(mInfo.title)) {
mFolderName.setText(mInfo.title);
mFolderName.setHint(null);
} else {
mFolderName.setText("");
- mFolderName.setHint(sHintText);
+ mFolderName.setHint(R.string.folder_hint_text);
}
-
// In case any children didn't come across during loading, clean up the folder accordingly
- mFolderIcon.post(new Runnable() {
- public void run() {
- if (getItemCount() <= 1) {
- replaceFolderWithFinalItem();
- }
+ mFolderIcon.post(() -> {
+ if (getItemCount() <= 1) {
+ replaceFolderWithFinalItem();
}
});
}
+
+ /**
+ * Show suggested folder title.
+ */
+ public void showSuggestedTitle(CharSequence suggestName) {
+ if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && mInfo.contents.size() == 2) {
+ if (!TextUtils.isEmpty(suggestName)) {
+ mFolderName.setHint(suggestName);
+ mFolderName.setText(suggestName);
+ mFolderName.showKeyboard();
+ mInfo.title = suggestName;
+ }
+ animateOpen();
+ }
+ }
+
/**
* Creates a new UserFolder, inflated from R.layout.user_folder.
*
@@ -431,9 +445,6 @@
}
private void startAnimation(final AnimatorSet a) {
- if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
- mCurrentAnimator.cancel();
- }
final Workspace workspace = mLauncher.getWorkspace();
final CellLayout currentCellLayout =
(CellLayout) workspace.getChildAt(workspace.getCurrentPage());
@@ -473,17 +484,49 @@
}
/**
+ * Opens the folder as part of a drag operation
+ */
+ public void beginExternalDrag() {
+ mIsExternalDrag = true;
+ mDragInProgress = true;
+
+ // Since this folder opened by another controller, it might not get onDrop or
+ // onDropComplete. Perform cleanup once drag-n-drop ends.
+ mDragController.addDragListener(this);
+
+ ArrayList<WorkspaceItemInfo> items = new ArrayList<>(mInfo.contents);
+ mEmptyCellRank = items.size();
+ items.add(null); // Add an empty spot at the end
+
+ animateOpen(items, mEmptyCellRank / mContent.itemsPerPage());
+ }
+
+ /**
* Opens the user folder described by the specified tag. The opening of the folder
* is animated relative to the specified View. If the View is null, no animation
* is played.
*/
public void animateOpen() {
+ animateOpen(mInfo.contents, 0);
+ }
+
+ /**
+ * Opens the user folder described by the specified tag. The opening of the folder
+ * is animated relative to the specified View. If the View is null, no animation
+ * is played.
+ */
+ private void animateOpen(List<WorkspaceItemInfo> items, int pageNo) {
Folder openFolder = getOpen(mLauncher);
if (openFolder != null && openFolder != this) {
// Close any open folder before opening a folder.
openFolder.close(true);
}
+ mContent.bindItems(items);
+ centerAboutIcon();
+ mItemsInvalidated = true;
+ updateTextViewFocus();
+
mIsOpen = true;
DragLayer dragLayer = mLauncher.getDragLayer();
@@ -500,23 +543,21 @@
}
mContent.completePendingPageChanges();
- if (!mDragInProgress) {
- // Open on the first page.
- mContent.snapToPageImmediately(0);
- }
+ mContent.snapToPageImmediately(pageNo);
// This is set to true in close(), but isn't reset to false until onDropCompleted(). This
// leads to an inconsistent state if you drag out of the folder and drag back in without
// dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice.
mDeleteFolderOnDropCompleted = false;
- centerAboutIcon();
-
+ if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
+ mCurrentAnimator.cancel();
+ }
AnimatorSet anim = new FolderAnimationManager(this, true /* isOpening */).getAnimator();
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
- mFolderIcon.setBackgroundVisible(false);
+ mFolderIcon.setIconVisible(false);
mFolderIcon.drawLeaveBehindIfExists();
}
@Override
@@ -524,7 +565,11 @@
mState = STATE_OPEN;
announceAccessibilityChanges();
- mLauncher.getUserEventDispatcher().resetElapsedContainerMillis("folder opened");
+ mLauncher.getUserEventDispatcher().logActionOnItem(
+ Touch.TAP,
+ Direction.NONE,
+ ItemType.FOLDER_ICON, mInfo.cellX, mInfo.cellY);
+
mContent.setFocusOnFirstChild();
}
});
@@ -570,20 +615,9 @@
if (mDragController.isDragging()) {
mDragController.forceTouchMove();
}
-
mContent.verifyVisibleHighResIcons(mContent.getNextPage());
}
- public void beginExternalDrag() {
- mEmptyCellRank = mContent.allocateRankForNewItem();
- mIsExternalDrag = true;
- mDragInProgress = true;
-
- // Since this folder opened by another controller, it might not get onDrop or
- // onDropComplete. Perform cleanup once drag-n-drop ends.
- mDragController.addDragListener(this);
- }
-
@Override
protected boolean isOfType(int type) {
return (type & TYPE_FOLDER) != 0;
@@ -619,6 +653,9 @@
}
private void animateClosed() {
+ if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
+ mCurrentAnimator.cancel();
+ }
AnimatorSet a = new FolderAnimationManager(this, false /* isOpening */).getAnimator();
a.addListener(new AnimatorListenerAdapter() {
@Override
@@ -646,7 +683,7 @@
clearFocus();
if (mFolderIcon != null) {
mFolderIcon.setVisibility(View.VISIBLE);
- mFolderIcon.setBackgroundVisible(true);
+ mFolderIcon.setIconVisible(true);
mFolderIcon.mFolderName.setTextVisibility(true);
if (wasAnimated) {
mFolderIcon.animateBgShadowAndStroke();
@@ -668,6 +705,8 @@
} else if (mDragInProgress) {
mDeleteFolderOnDropCompleted = true;
}
+ } else if (!mDragInProgress) {
+ mContent.unbindItems();
}
mSuppressFolderDeletion = false;
clearDragInfo();
@@ -822,9 +861,9 @@
}
}
+ @Override
public void onDropCompleted(final View target, final DragObject d,
final boolean success) {
-
if (success) {
if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) {
replaceFolderWithFinalItem();
@@ -834,9 +873,9 @@
WorkspaceItemInfo info = (WorkspaceItemInfo) d.dragInfo;
View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info)
? mCurrentDragView : mContent.createNewView(info);
- ArrayList<View> views = getItemsInReadingOrder();
+ ArrayList<View> views = getIconsInReadingOrder();
views.add(info.rank, icon);
- mContent.arrangeChildren(views, views.size());
+ mContent.arrangeChildren(views);
mItemsInvalidated = true;
try (SuppressInfoChanges s = new SuppressInfoChanges()) {
@@ -863,7 +902,6 @@
// Reordering may have occured, and we need to save the new item locations. We do this once
// at the end to prevent unnecessary database operations.
updateItemLocationsInDatabaseBatch();
-
// Use the item count to check for multi-page as the folder UI may not have
// been refreshed yet.
if (getItemCount() <= mContent.itemsPerPage()) {
@@ -874,16 +912,21 @@
}
private void updateItemLocationsInDatabaseBatch() {
- ArrayList<View> list = getItemsInReadingOrder();
- ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
- for (int i = 0; i < list.size(); i++) {
- View v = list.get(i);
- ItemInfo info = (ItemInfo) v.getTag();
- info.rank = i;
- items.add(info);
+ FolderGridOrganizer verifier = new FolderGridOrganizer(
+ mLauncher.getDeviceProfile().inv).setFolderInfo(mInfo);
+
+ ArrayList<ItemInfo> items = new ArrayList<>();
+ int total = mInfo.contents.size();
+ for (int i = 0; i < total; i++) {
+ WorkspaceItemInfo itemInfo = mInfo.contents.get(i);
+ if (verifier.updateRankAndPos(itemInfo, i)) {
+ items.add(itemInfo);
+ }
}
- mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0);
+ if (!items.isEmpty()) {
+ mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0);
+ }
}
public void notifyDrop() {
@@ -948,28 +991,16 @@
setPivotX(folderPivotX);
setPivotY(folderPivotY);
- mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() *
- (1.0f * folderPivotX / width));
- mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() *
- (1.0f * folderPivotY / height));
-
lp.width = width;
lp.height = height;
lp.x = left;
lp.y = top;
}
- public float getPivotXForIconAnimation() {
- return mFolderIconPivotX;
- }
- public float getPivotYForIconAnimation() {
- return mFolderIconPivotY;
- }
-
- private int getContentAreaHeight() {
+ protected int getContentAreaHeight() {
DeviceProfile grid = mLauncher.getDeviceProfile();
- int maxContentAreaHeight = grid.availableHeightPx
- - grid.getTotalWorkspacePadding().y - mFooterHeight;
+ int maxContentAreaHeight = grid.availableHeightPx - grid.getTotalWorkspacePadding().y
+ - mFooterHeight;
int height = Math.min(maxContentAreaHeight,
mContent.getDesiredHeight());
return Math.max(height, MIN_CONTENT_DIMEN);
@@ -1021,22 +1052,12 @@
* Rearranges the children based on their rank.
*/
public void rearrangeChildren() {
- rearrangeChildren(-1);
- }
-
- /**
- * Rearranges the children based on their rank.
- * @param itemCount if greater than the total children count, empty spaces are left at the end,
- * otherwise it is ignored.
- */
- public void rearrangeChildren(int itemCount) {
- ArrayList<View> views = getItemsInReadingOrder();
- mContent.arrangeChildren(views, Math.max(itemCount, views.size()));
+ mContent.arrangeChildren(getIconsInReadingOrder());
mItemsInvalidated = true;
}
public int getItemCount() {
- return mContent.getItemCount();
+ return mInfo.contents.size();
}
@Thunk void replaceFolderWithFinalItem() {
@@ -1044,7 +1065,7 @@
Runnable onCompleteRunnable = new Runnable() {
@Override
public void run() {
- int itemCount = mInfo.contents.size();
+ int itemCount = getItemCount();
if (itemCount <= 1) {
View newIcon = null;
@@ -1121,9 +1142,12 @@
return false;
}
});
+ } else {
+ setOnKeyListener(null);
}
}
+ @Override
public void onDrop(DragObject d, DragOptions options) {
// If the icon was dropped while the page was being scrolled, we need to compute
// the target location again such that the icon is placed of the final page.
@@ -1171,12 +1195,6 @@
// before creating the view, so that WorkspaceItemInfo is updated appropriately.
mLauncher.getModelWriter().addOrMoveItemInDatabase(
si, mInfo.id, 0, si.cellX, si.cellY);
-
- // We only need to update the locations if it doesn't get handled in
- // #onDropCompleted.
- if (d.dragSource != this) {
- updateItemLocationsInDatabaseBatch();
- }
mIsExternalDrag = false;
} else {
currentDragView = mCurrentDragView;
@@ -1203,7 +1221,13 @@
// Temporarily suppress the listener, as we did all the work already here.
try (SuppressInfoChanges s = new SuppressInfoChanges()) {
- mInfo.add(si, false);
+ mInfo.add(si, mEmptyCellRank, false);
+ }
+
+ // We only need to update the locations if it doesn't get handled in
+ // #onDropCompleted.
+ if (d.dragSource != this) {
+ updateItemLocationsInDatabaseBatch();
}
}
@@ -1226,22 +1250,29 @@
// to correspond to the animation of the icon back into the folder. This is
public void hideItem(WorkspaceItemInfo info) {
View v = getViewForInfo(info);
- v.setVisibility(INVISIBLE);
+ if (v != null) {
+ v.setVisibility(INVISIBLE);
+ }
}
public void showItem(WorkspaceItemInfo info) {
View v = getViewForInfo(info);
- v.setVisibility(VISIBLE);
+ if (v != null) {
+ v.setVisibility(VISIBLE);
+ }
}
@Override
public void onAdd(WorkspaceItemInfo item, int rank) {
- View view = mContent.createAndAddViewForRank(item, rank);
+ FolderGridOrganizer verifier = new FolderGridOrganizer(
+ mLauncher.getDeviceProfile().inv).setFolderInfo(mInfo);
+ verifier.updateRankAndPos(item, rank);
mLauncher.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX,
item.cellY);
+ updateItemLocationsInDatabaseBatch();
- ArrayList<View> items = new ArrayList<>(getItemsInReadingOrder());
- items.add(rank, view);
- mContent.arrangeChildren(items, items.size());
+ if (mContent.areViewsBound()) {
+ mContent.createAndAddViewForRank(item, rank);
+ }
mItemsInvalidated = true;
}
@@ -1264,13 +1295,7 @@
}
private View getViewForInfo(final WorkspaceItemInfo item) {
- return mContent.iterateOverItems(new ItemOperator() {
-
- @Override
- public boolean evaluate(ItemInfo info, View view) {
- return info == item;
- }
- });
+ return mContent.iterateOverItems((info, view) -> info == item);
}
@Override
@@ -1278,32 +1303,27 @@
updateTextViewFocus();
}
- @Override
- public void prepareAutoUpdate() {
- close(false);
+ /**
+ * Utility methods to iterate over items of the view
+ */
+ public void iterateOverItems(ItemOperator op) {
+ mContent.iterateOverItems(op);
}
- public void onTitleChanged(CharSequence title) {
- }
-
- public ArrayList<View> getItemsInReadingOrder() {
+ /**
+ * Returns the sorted list of all the icons in the folder
+ */
+ public ArrayList<View> getIconsInReadingOrder() {
if (mItemsInvalidated) {
mItemsInReadingOrder.clear();
- mContent.iterateOverItems(new ItemOperator() {
-
- @Override
- public boolean evaluate(ItemInfo info, View view) {
- mItemsInReadingOrder.add(view);
- return false;
- }
- });
+ mContent.iterateOverItems((i, v) -> !mItemsInReadingOrder.add(v));
mItemsInvalidated = false;
}
return mItemsInReadingOrder;
}
public List<BubbleTextView> getItemsOnPage(int page) {
- ArrayList<View> allItems = getItemsInReadingOrder();
+ ArrayList<View> allItems = getIconsInReadingOrder();
int lastPage = mContent.getPageCount() - 1;
int totalItemsInFolder = allItems.size();
int itemsPerPage = mContent.itemsPerPage();
@@ -1326,6 +1346,9 @@
if (hasFocus) {
startEditingFolderName();
} else {
+ if (isEditingName()) {
+ logEditFolderLabel();
+ }
mFolderName.dispatchBackKey();
}
}
@@ -1482,15 +1505,6 @@
return false;
}
- public static void setLocaleDependentFields(Resources res, boolean force) {
- if (sDefaultFolderName == null || force) {
- sDefaultFolderName = res.getString(R.string.folder_name);
- }
- if (sHintText == null || force) {
- sHintText = res.getString(R.string.folder_hint_text);
- }
- }
-
/**
* Alternative to using {@link #getClipToOutline()} as it only works with derivatives of
* rounded rect.
@@ -1512,4 +1526,39 @@
super.draw(canvas);
}
}
+
+ private void logEditFolderLabel() {
+ LauncherLogProto.LauncherEvent ev = new LauncherLogProto.LauncherEvent();
+ LauncherLogProto.Action action = new LauncherLogProto.Action();
+ action.type = LauncherLogProto.Action.Type.SOFT_KEYBOARD;
+ ev.action = action;
+
+ LauncherLogProto.Target edittext_target = new LauncherLogProto.Target();
+ edittext_target.type = LauncherLogProto.Target.Type.ITEM;
+ edittext_target.itemType = LauncherLogProto.ItemType.EDITTEXT;
+
+ LauncherLogProto.Target folder_target = new LauncherLogProto.Target();
+ folder_target.type = LauncherLogProto.Target.Type.CONTAINER;
+ folder_target.containerType = LauncherLogProto.ContainerType.FOLDER;
+ folder_target.pageIndex = mInfo.screenId;
+ folder_target.gridX = mInfo.cellX;
+ folder_target.gridY = mInfo.cellY;
+ folder_target.cardinality = mInfo.contents.size();
+
+ LauncherLogProto.Target parent_target = new LauncherLogProto.Target();
+ parent_target.type = LauncherLogProto.Target.Type.CONTAINER;
+ switch (mInfo.container) {
+ case CONTAINER_HOTSEAT:
+ parent_target.containerType = LauncherLogProto.ContainerType.HOTSEAT;
+ break;
+ case CONTAINER_DESKTOP:
+ parent_target.containerType = LauncherLogProto.ContainerType.WORKSPACE;
+ break;
+ default:
+ Log.e(TAG, String.format("Expected container to be either %s or %s but found %s.",
+ CONTAINER_HOTSEAT, CONTAINER_DESKTOP, mInfo.container));
+ }
+ ev.srcTarget = new LauncherLogProto.Target[]{edittext_target, folder_target, parent_target};
+ mLauncher.getUserEventDispatcher().dispatchUserEvent(ev, null);
+ }
}
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 962f215..1310d37 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -59,6 +59,8 @@
*/
public class FolderAnimationManager {
+ private static final int FOLDER_NAME_ALPHA_DURATION = 32;
+
private Folder mFolder;
private FolderPagedView mContent;
private GradientDrawable mFolderBackground;
@@ -79,7 +81,7 @@
private final TimeInterpolator mLargeFolderPreviewItemCloseInterpolator;
private final PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
-
+ private final FolderGridOrganizer mPreviewVerifier;
public FolderAnimationManager(Folder folder, boolean isOpening) {
mFolder = folder;
@@ -91,6 +93,7 @@
mContext = folder.getContext();
mLauncher = folder.mLauncher;
+ mPreviewVerifier = new FolderGridOrganizer(mLauncher.getDeviceProfile().inv);
mIsOpening = isOpening;
@@ -113,7 +116,7 @@
public AnimatorSet getAnimator() {
final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams();
ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule();
- final List<BubbleTextView> itemsInPreview = mFolderIcon.getPreviewItems();
+ final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage(0);
// Match position of the FolderIcon
final Rect folderIconPos = new Rect();
@@ -129,11 +132,19 @@
* scaleRelativeToDragLayer;
final float finalScale = 1f;
float scale = mIsOpening ? initialScale : finalScale;
- mFolder.setScaleX(scale);
- mFolder.setScaleY(scale);
mFolder.setPivotX(0);
mFolder.setPivotY(0);
+ // Scale the contents of the folder.
+ mFolder.mContent.setScaleX(scale);
+ mFolder.mContent.setScaleY(scale);
+ mFolder.mContent.setPivotX(0);
+ mFolder.mContent.setPivotY(0);
+ mFolder.mFooter.setScaleX(scale);
+ mFolder.mFooter.setScaleY(scale);
+ mFolder.mFooter.setPivotX(0);
+ mFolder.mFooter.setPivotY(0);
+
// We want to create a small X offset for the preview items, so that they follow their
// expected path to their final locations. ie. an icon should not move right, if it's final
// location is to its left. This value is arbitrarily defined.
@@ -142,14 +153,13 @@
previewItemOffsetX = (int) (lp.width * initialScale - initialSize - previewItemOffsetX);
}
- final int paddingOffsetX = (int) ((mFolder.getPaddingLeft() + mContent.getPaddingLeft())
- * initialScale);
- final int paddingOffsetY = (int) ((mFolder.getPaddingTop() + mContent.getPaddingTop())
- * initialScale);
+ final int paddingOffsetX = (int) (mContent.getPaddingLeft() * initialScale);
+ final int paddingOffsetY = (int) (mContent.getPaddingTop() * initialScale);
- int initialX = folderIconPos.left + mPreviewBackground.getOffsetX() - paddingOffsetX
- - previewItemOffsetX;
- int initialY = folderIconPos.top + mPreviewBackground.getOffsetY() - paddingOffsetY;
+ int initialX = folderIconPos.left + mFolder.getPaddingLeft()
+ + mPreviewBackground.getOffsetX() - paddingOffsetX - previewItemOffsetX;
+ int initialY = folderIconPos.top + mFolder.getPaddingTop()
+ + mPreviewBackground.getOffsetY() - paddingOffsetY;
final float xDistance = initialX - lp.x;
final float yDistance = initialY - lp.y;
@@ -163,11 +173,10 @@
// Set up the reveal animation that clips the Folder.
int totalOffsetX = paddingOffsetX + previewItemOffsetX;
- Rect startRect = new Rect(
- Math.round(totalOffsetX / initialScale),
- Math.round(paddingOffsetY / initialScale),
- Math.round((totalOffsetX + initialSize) / initialScale),
- Math.round((paddingOffsetY + initialSize) / initialScale));
+ Rect startRect = new Rect(totalOffsetX,
+ paddingOffsetY,
+ Math.round((totalOffsetX + initialSize)),
+ Math.round((paddingOffsetY + initialSize)));
Rect endRect = new Rect(0, 0, lp.width, lp.height);
float finalRadius = ResourceUtils.pxFromDp(2, mContext.getResources().getDisplayMetrics());
@@ -188,17 +197,46 @@
play(a, getAnimator(mFolder, View.TRANSLATION_X, xDistance, 0f));
play(a, getAnimator(mFolder, View.TRANSLATION_Y, yDistance, 0f));
- play(a, getAnimator(mFolder, SCALE_PROPERTY, initialScale, finalScale));
+ play(a, getAnimator(mFolder.mContent, SCALE_PROPERTY, initialScale, finalScale));
+ play(a, getAnimator(mFolder.mFooter, SCALE_PROPERTY, initialScale, finalScale));
play(a, getAnimator(mFolderBackground, "color", initialColor, finalColor));
play(a, mFolderIcon.mFolderName.createTextAlphaAnimator(!mIsOpening));
play(a, getShape().createRevealAnimator(
mFolder, startRect, endRect, finalRadius, !mIsOpening));
+ // Fade in the folder name, as the text can overlap the icons when grid size is small.
+ mFolder.mFolderName.setAlpha(mIsOpening ? 0f : 1f);
+ play(a, getAnimator(mFolder.mFolderName, View.ALPHA, 0, 1),
+ mIsOpening ? FOLDER_NAME_ALPHA_DURATION : 0,
+ mIsOpening ? mDuration - FOLDER_NAME_ALPHA_DURATION : FOLDER_NAME_ALPHA_DURATION);
+
+ // Translate the footer so that it tracks the bottom of the content.
+ float normalHeight = mFolder.getContentAreaHeight();
+ float scaledHeight = normalHeight * initialScale;
+ float diff = normalHeight - scaledHeight;
+ play(a, getAnimator(mFolder.mFooter, View.TRANSLATION_Y, -diff, 0f));
// Animate the elevation midway so that the shadow is not noticeable in the background.
int midDuration = mDuration / 2;
Animator z = getAnimator(mFolder, View.TRANSLATION_Z, -mFolder.getElevation(), 0);
play(a, z, mIsOpening ? midDuration : 0, midDuration);
+
+ // Store clip variables
+ CellLayout cellLayout = mContent.getCurrentCellLayout();
+ boolean folderClipChildren = mFolder.getClipChildren();
+ boolean folderClipToPadding = mFolder.getClipToPadding();
+ boolean contentClipChildren = mContent.getClipChildren();
+ boolean contentClipToPadding = mContent.getClipToPadding();
+ boolean cellLayoutClipChildren = cellLayout.getClipChildren();
+ boolean cellLayoutClipPadding = cellLayout.getClipToPadding();
+
+ mFolder.setClipChildren(false);
+ mFolder.setClipToPadding(false);
+ mContent.setClipChildren(false);
+ mContent.setClipToPadding(false);
+ cellLayout.setClipChildren(false);
+ cellLayout.setClipToPadding(false);
+
a.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -206,8 +244,20 @@
mFolder.setTranslationX(0.0f);
mFolder.setTranslationY(0.0f);
mFolder.setTranslationZ(0.0f);
- mFolder.setScaleX(1f);
- mFolder.setScaleY(1f);
+ mFolder.mContent.setScaleX(1f);
+ mFolder.mContent.setScaleY(1f);
+ mFolder.mFooter.setScaleX(1f);
+ mFolder.mFooter.setScaleY(1f);
+ mFolder.mFooter.setTranslationX(0f);
+ mFolder.mFolderName.setAlpha(1f);
+
+ mFolder.setClipChildren(folderClipChildren);
+ mFolder.setClipToPadding(folderClipToPadding);
+ mContent.setClipChildren(contentClipChildren);
+ mContent.setClipToPadding(contentClipToPadding);
+ cellLayout.setClipChildren(cellLayoutClipChildren);
+ cellLayout.setClipToPadding(cellLayoutClipPadding);
+
}
});
@@ -226,15 +276,22 @@
}
/**
+ * Returns the list of "preview items" on {@param page}.
+ */
+ private List<BubbleTextView> getPreviewIconsOnPage(int page) {
+ return mPreviewVerifier.setFolderInfo(mFolder.mInfo)
+ .previewItemsForPage(page, mFolder.getIconsInReadingOrder());
+ }
+
+ /**
* Animate the items on the current page.
*/
private void addPreviewItemAnimators(AnimatorSet animatorSet, final float folderScale,
int previewItemOffsetX, int previewItemOffsetY) {
ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule();
boolean isOnFirstPage = mFolder.mContent.getCurrentPage() == 0;
- final List<BubbleTextView> itemsInPreview = isOnFirstPage
- ? mFolderIcon.getPreviewItems()
- : mFolderIcon.getPreviewItemsOnPage(mFolder.mContent.getCurrentPage());
+ final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage(
+ isOnFirstPage ? 0 : mFolder.mContent.getCurrentPage());
final int numItemsInPreview = itemsInPreview.size();
final int numItemsInFirstPagePreview = isOnFirstPage
? numItemsInPreview : MAX_NUM_ITEMS_IN_PREVIEW;
diff --git a/src/com/android/launcher3/folder/FolderGridOrganizer.java b/src/com/android/launcher3/folder/FolderGridOrganizer.java
new file mode 100644
index 0000000..9d14a5f
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderGridOrganizer.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2019 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.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+
+import android.graphics.Point;
+
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class for managing item positions in a folder based on rank
+ */
+public class FolderGridOrganizer {
+
+ private final Point mPoint = new Point();
+ private final int mMaxCountX;
+ private final int mMaxCountY;
+ private final int mMaxItemsPerPage;
+
+ private int mNumItemsInFolder;
+ private int mCountX;
+ private int mCountY;
+ private boolean mDisplayingUpperLeftQuadrant = false;
+
+ /**
+ * Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work.
+ */
+ public FolderGridOrganizer(InvariantDeviceProfile profile) {
+ mMaxCountX = profile.numFolderColumns;
+ mMaxCountY = profile.numFolderRows;
+ mMaxItemsPerPage = mMaxCountX * mMaxCountY;
+ }
+
+ /**
+ * Updates the organizer with the provided folder info
+ */
+ public FolderGridOrganizer setFolderInfo(FolderInfo info) {
+ return setContentSize(info.contents.size());
+ }
+
+ /**
+ * Updates the organizer to reflect the content size
+ */
+ public FolderGridOrganizer setContentSize(int contentSize) {
+ if (contentSize != mNumItemsInFolder) {
+ calculateGridSize(contentSize);
+
+ mDisplayingUpperLeftQuadrant = contentSize > MAX_NUM_ITEMS_IN_PREVIEW;
+ mNumItemsInFolder = contentSize;
+ }
+ return this;
+ }
+
+ public int getCountX() {
+ return mCountX;
+ }
+
+ public int getCountY() {
+ return mCountY;
+ }
+
+ public int getMaxItemsPerPage() {
+ return mMaxItemsPerPage;
+ }
+
+ /**
+ * Calculates the grid size such that {@param count} items can fit in the grid.
+ * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while
+ * maintaining the restrictions of {@link #mMaxCountX} & {@link #mMaxCountY}.
+ */
+ private void calculateGridSize(int count) {
+ boolean done;
+ int gridCountX = mCountX;
+ int gridCountY = mCountY;
+
+ if (count >= mMaxItemsPerPage) {
+ gridCountX = mMaxCountX;
+ gridCountY = mMaxCountY;
+ done = true;
+ } else {
+ done = false;
+ }
+
+ while (!done) {
+ int oldCountX = gridCountX;
+ int oldCountY = gridCountY;
+ if (gridCountX * gridCountY < count) {
+ // Current grid is too small, expand it
+ if ((gridCountX <= gridCountY || gridCountY == mMaxCountY)
+ && gridCountX < mMaxCountX) {
+ gridCountX++;
+ } else if (gridCountY < mMaxCountY) {
+ gridCountY++;
+ }
+ if (gridCountY == 0) gridCountY++;
+ } else if ((gridCountY - 1) * gridCountX >= count && gridCountY >= gridCountX) {
+ gridCountY = Math.max(0, gridCountY - 1);
+ } else if ((gridCountX - 1) * gridCountY >= count) {
+ gridCountX = Math.max(0, gridCountX - 1);
+ }
+ done = gridCountX == oldCountX && gridCountY == oldCountY;
+ }
+
+ mCountX = gridCountX;
+ mCountY = gridCountY;
+ }
+
+ /**
+ * Updates the item's cellX, cellY and rank corresponding to the provided rank.
+ * @return true if there was any change
+ */
+ public boolean updateRankAndPos(ItemInfo item, int rank) {
+ Point pos = getPosForRank(rank);
+ if (!pos.equals(item.cellX, item.cellY) || rank != item.rank) {
+ item.rank = rank;
+ item.cellX = pos.x;
+ item.cellY = pos.y;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the position of the item in the grid
+ */
+ public Point getPosForRank(int rank) {
+ int pagePos = rank % mMaxItemsPerPage;
+ mPoint.x = pagePos % mCountX;
+ mPoint.y = pagePos / mCountX;
+ return mPoint;
+ }
+
+ /**
+ * Returns the preview items for the provided pageNo using the full list of contents
+ */
+ public <T, R extends T> ArrayList<R> previewItemsForPage(int page, List<T> contents) {
+ ArrayList<R> result = new ArrayList<>();
+ int itemsPerPage = mCountX * mCountY;
+ int start = itemsPerPage * page;
+ int end = Math.min(start + itemsPerPage, contents.size());
+
+ for (int i = start, rank = 0; i < end; i++, rank++) {
+ if (isItemInPreview(page, rank)) {
+ result.add((R) contents.get(i));
+ }
+
+ if (result.size() == MAX_NUM_ITEMS_IN_PREVIEW) {
+ break;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns whether the item with rank is in the default Folder icon preview.
+ */
+ public boolean isItemInPreview(int rank) {
+ return isItemInPreview(0, rank);
+ }
+
+ /**
+ * @param page The page the item is on.
+ * @param rank The rank of the item.
+ * @return True iff the icon is in the 2x2 upper left quadrant of the Folder.
+ */
+ public boolean isItemInPreview(int page, int rank) {
+ // First page items are laid out such that the first 4 items are always in the upper
+ // left quadrant. For all other pages, we need to check the row and col.
+ if (page > 0 || mDisplayingUpperLeftQuadrant) {
+ int col = rank % mCountX;
+ int row = rank / mCountX;
+ return col < 2 && row < 2;
+ }
+ return rank < MAX_NUM_ITEMS_IN_PREVIEW;
+ }
+}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 250169c..fd6d1e3 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -58,22 +58,26 @@
import com.android.launcher3.Workspace;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.FolderDotInfo;
import com.android.launcher3.dragndrop.BaseItemDragListener;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.IconLabelDotView;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Predicate;
/**
* An icon that can appear on in the workspace representing an {@link Folder}.
*/
-public class FolderIcon extends FrameLayout implements FolderListener {
+public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView {
@Thunk Launcher mLauncher;
@Thunk Folder mFolder;
@@ -95,11 +99,11 @@
PreviewBackground mBackground = new PreviewBackground();
private boolean mBackgroundIsVisible = true;
- FolderIconPreviewVerifier mPreviewVerifier;
+ FolderGridOrganizer mPreviewVerifier;
ClippedFolderIconLayoutRule mPreviewLayoutRule;
private PreviewItemManager mPreviewItemManager;
private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
- private List<BubbleTextView> mCurrentPreviewItems = new ArrayList<>();
+ private List<WorkspaceItemInfo> mCurrentPreviewItems = new ArrayList<>();
boolean mAnimating = false;
@@ -107,6 +111,7 @@
private Alarm mOpenAlarm = new Alarm();
+ private boolean mForceHideDot;
@ViewDebug.ExportedProperty(category = "launcher", deepExport = true)
private FolderDotInfo mDotInfo = new FolderDotInfo();
private DotRenderer mDotRenderer;
@@ -173,7 +178,7 @@
icon.setOnClickListener(ItemClickHandler.INSTANCE);
icon.mInfo = folderInfo;
icon.mLauncher = launcher;
- icon.mDotRenderer = grid.mDotRenderer;
+ icon.mDotRenderer = grid.mDotRendererWorkSpace;
icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
Folder folder = Folder.fromXml(launcher);
folder.setDragController(launcher.getDragController());
@@ -212,7 +217,7 @@
private void setFolder(Folder folder) {
mFolder = folder;
- mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv);
+ mPreviewVerifier = new FolderGridOrganizer(mLauncher.getDeviceProfile().inv);
mPreviewVerifier.setFolderInfo(mFolder.getInfo());
updatePreviewItems(false);
}
@@ -230,11 +235,7 @@
}
public void addItem(WorkspaceItemInfo item) {
- addItem(item, true);
- }
-
- public void addItem(WorkspaceItemInfo item, boolean animate) {
- mInfo.add(item, animate);
+ mInfo.add(item, true);
}
public void removeItem(WorkspaceItemInfo item, boolean animate) {
@@ -259,7 +260,6 @@
OnAlarmListener mOnOpenListener = new OnAlarmListener() {
public void onAlarm(Alarm alarm) {
mFolder.beginExternalDrag();
- mFolder.animateOpen();
}
};
@@ -294,8 +294,7 @@
}
private void onDrop(final WorkspaceItemInfo item, DragView animateView, Rect finalRect,
- float scaleRelativeToDragLayer, int index,
- boolean itemReturnedOnFailedDrop) {
+ float scaleRelativeToDragLayer, int index, boolean itemReturnedOnFailedDrop) {
item.cellX = -1;
item.cellY = -1;
@@ -326,18 +325,17 @@
int numItemsInPreview = Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index + 1);
boolean itemAdded = false;
if (itemReturnedOnFailedDrop || index >= MAX_NUM_ITEMS_IN_PREVIEW) {
- List<BubbleTextView> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems);
- addItem(item, false);
+ List<WorkspaceItemInfo> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems);
+ mInfo.add(item, index, false);
mCurrentPreviewItems.clear();
- mCurrentPreviewItems.addAll(getPreviewItems());
+ mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0));
if (!oldPreviewItems.equals(mCurrentPreviewItems)) {
- for (int i = 0; i < mCurrentPreviewItems.size(); ++i) {
- if (mCurrentPreviewItems.get(i).getTag().equals(item)) {
- // If the item dropped is going to be in the preview, we update the
- // index here to reflect its position in the preview.
- index = i;
- }
+ int newIndex = mCurrentPreviewItems.indexOf(item);
+ if (newIndex >= 0) {
+ // If the item dropped is going to be in the preview, we update the
+ // index here to reflect its position in the preview.
+ index = newIndex;
}
mPreviewItemManager.hidePreviewItem(index, true);
@@ -349,13 +347,13 @@
}
if (!itemAdded) {
- addItem(item);
+ mInfo.add(item, index, true);
}
int[] center = new int[2];
float scale = getLocalCenterForIndex(index, numItemsInPreview, center);
- center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]);
- center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]);
+ center[0] = Math.round(scaleRelativeToDragLayer * center[0]);
+ center[1] = Math.round(scaleRelativeToDragLayer * center[1]);
to.offset(center[0] - animateView.getMeasuredWidth() / 2,
center[1] - animateView.getMeasuredHeight() / 2);
@@ -372,12 +370,17 @@
if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
final int finalIndex = index;
- postDelayed(new Runnable() {
- public void run() {
- mPreviewItemManager.hidePreviewItem(finalIndex, false);
- mFolder.showItem(item);
- invalidate();
- }
+
+ String[] suggestedNameOut = new String[1];
+ if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
+ Executors.UI_HELPER_EXECUTOR.post(() -> mLauncher.getFolderNameProvider()
+ .getSuggestedFolderName(getContext(), mInfo.contents, suggestedNameOut));
+ }
+ postDelayed(() -> {
+ mPreviewItemManager.hidePreviewItem(finalIndex, false);
+ mFolder.showItem(item);
+ invalidate();
+ mFolder.showSuggestedTitle(suggestedNameOut[0]);
}, DROP_IN_ANIMATION_DURATION);
} else {
addItem(item);
@@ -396,7 +399,8 @@
item = (WorkspaceItemInfo) d.dragInfo;
}
mFolder.notifyDrop();
- onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(),
+ onDrop(item, d.dragView, null, 1.0f,
+ itemReturnedOnFailedDrop ? item.rank : mInfo.contents.size(),
itemReturnedOnFailedDrop);
}
@@ -409,6 +413,20 @@
return mPreviewLayoutRule;
}
+ @Override
+ public void setForceHideDot(boolean forceHideDot) {
+ if (mForceHideDot == forceHideDot) {
+ return;
+ }
+ mForceHideDot = forceHideDot;
+
+ if (forceHideDot) {
+ invalidate();
+ } else if (hasDot()) {
+ animateDotScale(0, 1);
+ }
+ }
+
/**
* Sets mDotScale to 1 or 0, animating if wasDotted or isDotted is false
* (the dot is being added or removed).
@@ -468,7 +486,8 @@
mBackground.setInvalidateDelegate(this);
}
- public void setBackgroundVisible(boolean visible) {
+ @Override
+ public void setIconVisible(boolean visible) {
mBackgroundIsVisible = visible;
invalidate();
}
@@ -493,8 +512,7 @@
mBackground.drawBackground(canvas);
}
- if (mFolder == null) return;
- if (mFolder.getItemCount() == 0 && !mAnimating) return;
+ if (mCurrentPreviewItems.isEmpty() && !mAnimating) return;
final int saveCount = canvas.save();
canvas.clipPath(mBackground.getClipPath());
@@ -509,7 +527,7 @@
}
public void drawDot(Canvas canvas) {
- if ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0) {
+ if (!mForceHideDot && ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0)) {
Rect iconBounds = mDotParams.iconBounds;
BubbleTextView.getIconBounds(this, iconBounds,
mLauncher.getWallpaperDeviceProfile().iconSizePx);
@@ -536,31 +554,10 @@
}
/**
- * Returns the list of preview items displayed in the icon.
+ * Returns the list of items which should be visible in the preview
*/
- public List<BubbleTextView> getPreviewItems() {
- return getPreviewItemsOnPage(0);
- }
-
- /**
- * Returns the list of "preview items" on {@param page}.
- */
- public List<BubbleTextView> getPreviewItemsOnPage(int page) {
- mPreviewVerifier.setFolderInfo(mFolder.getInfo());
-
- List<BubbleTextView> itemsToDisplay = new ArrayList<>();
- List<BubbleTextView> itemsOnPage = mFolder.getItemsOnPage(page);
- int numItems = itemsOnPage.size();
- for (int rank = 0; rank < numItems; ++rank) {
- if (mPreviewVerifier.isItemInPreview(page, rank)) {
- itemsToDisplay.add(itemsOnPage.get(rank));
- }
-
- if (itemsToDisplay.size() == MAX_NUM_ITEMS_IN_PREVIEW) {
- break;
- }
- }
- return itemsToDisplay;
+ public List<WorkspaceItemInfo> getPreviewItemsOnPage(int page) {
+ return mPreviewVerifier.setFolderInfo(mInfo).previewItemsForPage(page, mInfo.contents);
}
@Override
@@ -578,11 +575,14 @@
private void updatePreviewItems(boolean animate) {
mPreviewItemManager.updatePreviewItems(animate);
mCurrentPreviewItems.clear();
- mCurrentPreviewItems.addAll(getPreviewItems());
+ mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0));
}
- @Override
- public void prepareAutoUpdate() {
+ /**
+ * Updates the preview items which match the provided condition
+ */
+ public void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) {
+ mPreviewItemManager.updatePreviewItems(itemCheck);
}
@Override
@@ -605,7 +605,6 @@
requestLayout();
}
- @Override
public void onTitleChanged(CharSequence title) {
mFolderName.setText(title);
setContentDescription(getContext().getString(R.string.folder_name_format, title));
diff --git a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
deleted file mode 100644
index 4c84e35..0000000
--- a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
+++ /dev/null
@@ -1,89 +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.folder;
-
-import android.util.Log;
-
-import com.android.launcher3.FolderInfo;
-import com.android.launcher3.InvariantDeviceProfile;
-
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
-
-/**
- * Verifies whether an item in a Folder is displayed in the FolderIcon preview.
- */
-public class FolderIconPreviewVerifier {
-
- private static final String TAG = "FolderPreviewVerifier";
-
- private final int mMaxGridCountX;
- private final int mMaxGridCountY;
- private final int mMaxItemsPerPage;
- private final int[] mGridSize = new int[] { 1, 1 };
-
- private int mNumItemsInFolder;
- private int mGridCountX;
- private boolean mDisplayingUpperLeftQuadrant = false;
-
- /**
- * Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work.
- */
- public FolderIconPreviewVerifier(InvariantDeviceProfile profile) {
- mMaxGridCountX = profile.numFolderColumns;
- mMaxGridCountY = profile.numFolderRows;
- mMaxItemsPerPage = mMaxGridCountX * mMaxGridCountY;
- }
-
- public void setFolderInfo(FolderInfo info) {
- int numItemsInFolder = info.contents.size();
- if (numItemsInFolder != mNumItemsInFolder) {
- FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, mMaxGridCountX,
- mMaxGridCountY, mMaxItemsPerPage, mGridSize);
- mGridCountX = mGridSize[0];
-
- mDisplayingUpperLeftQuadrant = numItemsInFolder > MAX_NUM_ITEMS_IN_PREVIEW;
- mNumItemsInFolder = numItemsInFolder;
- }
- }
-
- /**
- * Returns whether the item with {@param rank} is in the default Folder icon preview.
- */
- public boolean isItemInPreview(int rank) {
- return isItemInPreview(0, rank);
- }
-
- /**
- * @param page The page the item is on.
- * @param rank The rank of the item.
- * @return True iff the icon is in the 2x2 upper left quadrant of the Folder.
- */
- public boolean isItemInPreview(int page, int rank) {
- if (mGridSize[0] == 1) {
- Log.w(TAG, "setFolderInfo not called before checking if item is in preview.");
- }
-
- // First page items are laid out such that the first 4 items are always in the upper
- // left quadrant. For all other pages, we need to check the row and col.
- if (page > 0 || mDisplayingUpperLeftQuadrant) {
- int col = rank % mGridCountX;
- int row = rank / mGridCountX;
- return col < 2 && row < 2;
- }
- return rank < MAX_NUM_ITEMS_IN_PREVIEW;
- }
-}
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
new file mode 100644
index 0000000..0a1221e
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 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 android.content.ComponentName;
+import android.content.Context;
+
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.WorkspaceItemInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Locates provider for the folder name.
+ */
+public class FolderNameProvider {
+
+ /**
+ * Returns suggested folder name.
+ */
+ public CharSequence getSuggestedFolderName(Context context,
+ ArrayList<WorkspaceItemInfo> workspaceItemInfos, CharSequence[] suggestName) {
+ // Currently only run the algorithm on initial folder creation.
+ // For more than 2 items in the folder, the ranking algorithm for finding
+ // candidate folder name should be rewritten.
+ if (workspaceItemInfos.size() == 2) {
+ ComponentName cmp1 = workspaceItemInfos.get(0).getTargetComponent();
+ ComponentName cmp2 = workspaceItemInfos.get(1).getTargetComponent();
+
+ String pkgName0 = cmp1 == null ? "" : cmp1.getPackageName();
+ String pkgName1 = cmp2 == null ? "" : cmp2.getPackageName();
+ // If the two icons are from the same package,
+ // then assign the main icon's name
+ if (pkgName0.equals(pkgName1)) {
+ WorkspaceItemInfo wInfo0 = workspaceItemInfos.get(0);
+ WorkspaceItemInfo wInfo1 = workspaceItemInfos.get(1);
+ if (workspaceItemInfos.get(0).itemType == Favorites.ITEM_TYPE_APPLICATION) {
+ suggestName[0] = wInfo0.title;
+ } else if (wInfo1.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+ suggestName[0] = wInfo1.title;
+ }
+ return suggestName[0];
+ // two icons are all shortcuts. Don't assign title
+ }
+ }
+ return suggestName[0];
+ }
+}
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 57105e7..3b5fd59 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -24,10 +24,10 @@
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewDebug;
+import com.android.launcher3.BaseActivity;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
@@ -38,18 +38,22 @@
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.pageindicators.PageIndicatorDots;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.ViewCache;
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
+import java.util.function.ToIntFunction;
+import java.util.stream.Collectors;
public class FolderPagedView extends PagedView<PageIndicatorDots> {
@@ -68,17 +72,12 @@
public final boolean mIsRtl;
- private final LayoutInflater mInflater;
private final ViewGroupFocusHelper mFocusIndicatorHelper;
@Thunk final ArrayMap<View, Runnable> mPendingAnimations = new ArrayMap<>();
- @ViewDebug.ExportedProperty(category = "launcher")
- private final int mMaxCountX;
- @ViewDebug.ExportedProperty(category = "launcher")
- private final int mMaxCountY;
- @ViewDebug.ExportedProperty(category = "launcher")
- private final int mMaxItemsPerPage;
+ private final FolderGridOrganizer mOrganizer;
+ private final ViewCache mViewCache;
private int mAllocatedContentSize;
@ViewDebug.ExportedProperty(category = "launcher")
@@ -88,20 +87,20 @@
private Folder mFolder;
+ // If the views are attached to the folder or not. A folder should be bound when its
+ // animating or is open.
+ private boolean mViewsBound = false;
+
public FolderPagedView(Context context, AttributeSet attrs) {
super(context, attrs);
InvariantDeviceProfile profile = LauncherAppState.getIDP(context);
- mMaxCountX = profile.numFolderColumns;
- mMaxCountY = profile.numFolderRows;
-
- mMaxItemsPerPage = mMaxCountX * mMaxCountY;
-
- mInflater = LayoutInflater.from(context);
+ mOrganizer = new FolderGridOrganizer(profile);
mIsRtl = Utilities.isRtl(getResources());
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
+ mViewCache = BaseActivity.fromContext(context).getViewCache();
}
public void setFolder(Folder folder) {
@@ -111,57 +110,13 @@
}
/**
- * Calculates the grid size such that {@param count} items can fit in the grid.
- * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while
- * maintaining the restrictions of {@link #mMaxCountX} & {@link #mMaxCountY}.
- */
- public static void calculateGridSize(int count, int countX, int countY, int maxCountX,
- int maxCountY, int maxItemsPerPage, int[] out) {
- boolean done;
- int gridCountX = countX;
- int gridCountY = countY;
-
- if (count >= maxItemsPerPage) {
- gridCountX = maxCountX;
- gridCountY = maxCountY;
- done = true;
- } else {
- done = false;
- }
-
- while (!done) {
- int oldCountX = gridCountX;
- int oldCountY = gridCountY;
- if (gridCountX * gridCountY < count) {
- // Current grid is too small, expand it
- if ((gridCountX <= gridCountY || gridCountY == maxCountY)
- && gridCountX < maxCountX) {
- gridCountX++;
- } else if (gridCountY < maxCountY) {
- gridCountY++;
- }
- if (gridCountY == 0) gridCountY++;
- } else if ((gridCountY - 1) * gridCountX >= count && gridCountY >= gridCountX) {
- gridCountY = Math.max(0, gridCountY - 1);
- } else if ((gridCountX - 1) * gridCountY >= count) {
- gridCountX = Math.max(0, gridCountX - 1);
- }
- done = gridCountX == oldCountX && gridCountY == oldCountY;
- }
-
- out[0] = gridCountX;
- out[1] = gridCountY;
- }
-
- /**
* Sets up the grid size such that {@param count} items can fit in the grid.
*/
- public void setupContentDimensions(int count) {
+ private void setupContentDimensions(int count) {
mAllocatedContentSize = count;
- calculateGridSize(count, mGridCountX, mGridCountY, mMaxCountX, mMaxCountY, mMaxItemsPerPage,
- sTmpArray);
- mGridCountX = sTmpArray[0];
- mGridCountY = sTmpArray[1];
+ mOrganizer.setContentSize(count);
+ mGridCountX = mOrganizer.getCountX();
+ mGridCountY = mOrganizer.getCountY();
// Update grid size
for (int i = getPageCount() - 1; i >= 0; i--) {
@@ -178,35 +133,50 @@
/**
* Binds items to the layout.
*/
- public void bindItems(ArrayList<WorkspaceItemInfo> items) {
- ArrayList<View> icons = new ArrayList<>();
- for (WorkspaceItemInfo item : items) {
- icons.add(createNewView(item));
+ public void bindItems(List<WorkspaceItemInfo> items) {
+ if (mViewsBound) {
+ unbindItems();
}
- arrangeChildren(icons, icons.size(), false);
- }
-
- public void allocateSpaceForRank(int rank) {
- ArrayList<View> views = new ArrayList<>(mFolder.getItemsInReadingOrder());
- views.add(rank, null);
- arrangeChildren(views, views.size(), false);
+ arrangeChildren(items.stream().map(this::createNewView).collect(Collectors.toList()));
+ mViewsBound = true;
}
/**
- * Create space for a new item at the end, and returns the rank for that item.
- * Also sets the current page to the last page.
+ * Removes all the icons from the folder
*/
- public int allocateRankForNewItem() {
- int rank = getItemCount();
- allocateSpaceForRank(rank);
- setCurrentPage(rank / mMaxItemsPerPage);
- return rank;
+ public void unbindItems() {
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ CellLayout page = (CellLayout) getChildAt(i);
+ ShortcutAndWidgetContainer container = page.getShortcutsAndWidgets();
+ for (int j = container.getChildCount() - 1; j >= 0; j--) {
+ mViewCache.recycleView(R.layout.folder_application, container.getChildAt(j));
+ }
+ page.removeAllViews();
+ mViewCache.recycleView(R.layout.folder_page, page);
+ }
+ removeAllViews();
+ mViewsBound = false;
}
+ /**
+ * Returns true if the icons are bound to the folder
+ */
+ public boolean areViewsBound() {
+ return mViewsBound;
+ }
+
+ /**
+ * Creates and adds an icon corresponding to the provided rank
+ * @return the created icon
+ */
public View createAndAddViewForRank(WorkspaceItemInfo item, int rank) {
View icon = createNewView(item);
- allocateSpaceForRank(rank);
- addViewForRank(icon, item, rank);
+ if (!mViewsBound) {
+ return icon;
+ }
+ ArrayList<View> views = new ArrayList<>(mFolder.getIconsInReadingOrder());
+ views.add(rank, icon);
+ arrangeChildren(views);
return icon;
}
@@ -215,31 +185,33 @@
* related attributes. It assumes that {@param item} is already attached to the view.
*/
public void addViewForRank(View view, WorkspaceItemInfo item, int rank) {
- int pagePos = rank % mMaxItemsPerPage;
- int pageNo = rank / mMaxItemsPerPage;
-
- item.rank = rank;
- item.cellX = pagePos % mGridCountX;
- item.cellY = pagePos / mGridCountX;
+ int pageNo = rank / mOrganizer.getMaxItemsPerPage();
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
- lp.cellX = item.cellX;
- lp.cellY = item.cellY;
+ lp.setXY(mOrganizer.getPosForRank(rank));
getPageAt(pageNo).addViewToCellLayout(view, -1, item.getViewId(), lp, true);
}
@SuppressLint("InflateParams")
public View createNewView(WorkspaceItemInfo item) {
- final BubbleTextView textView = (BubbleTextView) mInflater.inflate(
- R.layout.folder_application, null, false);
+ if (item == null) {
+ return null;
+ }
+ final BubbleTextView textView = mViewCache.getView(
+ R.layout.folder_application, getContext(), null);
textView.applyFromWorkspaceItem(item);
- textView.setHapticFeedbackEnabled(false);
textView.setOnClickListener(ItemClickHandler.INSTANCE);
textView.setOnLongClickListener(mFolder);
textView.setOnFocusChangeListener(mFocusIndicatorHelper);
-
- textView.setLayoutParams(new CellLayout.LayoutParams(
- item.cellX, item.cellY, item.spanX, item.spanY));
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) textView.getLayoutParams();
+ if (lp == null) {
+ textView.setLayoutParams(new CellLayout.LayoutParams(
+ item.cellX, item.cellY, item.spanX, item.spanY));
+ } else {
+ lp.cellX = item.cellX;
+ lp.cellY = item.cellY;
+ lp.cellHSpan = lp.cellVSpan = 1;
+ }
return textView;
}
@@ -254,7 +226,7 @@
private CellLayout createAndAddNewPage() {
DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
- CellLayout page = (CellLayout) mInflater.inflate(R.layout.folder_page, this, false);
+ CellLayout page = mViewCache.getView(R.layout.folder_page, getContext(), this);
page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
page.setInvertIfRtl(true);
@@ -295,37 +267,28 @@
* page.
*
* @param list the ordered list of children.
- * @param itemCount if greater than the total children count, empty spaces are left
- * at the end, otherwise it is ignored.
- *
*/
- public void arrangeChildren(ArrayList<View> list, int itemCount) {
- arrangeChildren(list, itemCount, true);
- }
-
@SuppressLint("RtlHardcoded")
- private void arrangeChildren(ArrayList<View> list, int itemCount, boolean saveChanges) {
+ public void arrangeChildren(List<View> list) {
+ int itemCount = list.size();
ArrayList<CellLayout> pages = new ArrayList<>();
for (int i = 0; i < getChildCount(); i++) {
CellLayout page = (CellLayout) getChildAt(i);
page.removeAllViews();
pages.add(page);
}
+ mOrganizer.setFolderInfo(mFolder.getInfo());
setupContentDimensions(itemCount);
Iterator<CellLayout> pageItr = pages.iterator();
CellLayout currentPage = null;
int position = 0;
- int newX, newY, rank;
+ int rank = 0;
- FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(
- Launcher.getLauncher(getContext()).getDeviceProfile().inv);
- verifier.setFolderInfo(mFolder.getInfo());
- rank = 0;
for (int i = 0; i < itemCount; i++) {
View v = list.size() > i ? list.get(i) : null;
- if (currentPage == null || position >= mMaxItemsPerPage) {
+ if (currentPage == null || position >= mOrganizer.getMaxItemsPerPage()) {
// Next page
if (pageItr.hasNext()) {
currentPage = pageItr.next();
@@ -337,28 +300,16 @@
if (v != null) {
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
- newX = position % mGridCountX;
- newY = position / mGridCountX;
ItemInfo info = (ItemInfo) v.getTag();
- if (info.cellX != newX || info.cellY != newY || info.rank != rank) {
- info.cellX = newX;
- info.cellY = newY;
- info.rank = rank;
- if (saveChanges) {
- mFolder.mLauncher.getModelWriter().addOrMoveItemInDatabase(info,
- mFolder.mInfo.id, 0, info.cellX, info.cellY);
- }
- }
- lp.cellX = info.cellX;
- lp.cellY = info.cellY;
+ lp.setXY(mOrganizer.getPosForRank(rank));
currentPage.addViewToCellLayout(v, -1, info.getViewId(), lp, true);
- if (verifier.isItemInPreview(rank) && v instanceof BubbleTextView) {
+ if (mOrganizer.isItemInPreview(rank) && v instanceof BubbleTextView) {
((BubbleTextView) v).verifyHighRes();
}
}
- rank ++;
+ rank++;
position++;
}
@@ -391,16 +342,6 @@
(getPageAt(0).getDesiredHeight() + getPaddingTop() + getPaddingBottom()) : 0;
}
- public int getItemCount() {
- int lastPageIndex = getChildCount() - 1;
- if (lastPageIndex < 0) {
- // If there are no pages, nothing has yet been added to the folder.
- return 0;
- }
- return getPageAt(lastPageIndex).getShortcutsAndWidgets().getChildCount()
- + lastPageIndex * mMaxItemsPerPage;
- }
-
/**
* @return the rank of the cell nearest to the provided pixel position.
*/
@@ -412,31 +353,28 @@
sTmpArray[0] = page.getCountX() - sTmpArray[0] - 1;
}
return Math.min(mAllocatedContentSize - 1,
- pageIndex * mMaxItemsPerPage + sTmpArray[1] * mGridCountX + sTmpArray[0]);
+ pageIndex * mOrganizer.getMaxItemsPerPage()
+ + sTmpArray[1] * mGridCountX + sTmpArray[0]);
}
public View getFirstItem() {
- if (getChildCount() < 1) {
- return null;
- }
- ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets();
- if (mGridCountX > 0) {
- return currContainer.getChildAt(0, 0);
- } else {
- return currContainer.getChildAt(0);
- }
+ return getViewInCurrentPage(c -> 0);
}
public View getLastItem() {
+ return getViewInCurrentPage(c -> c.getChildCount() - 1);
+ }
+
+ private View getViewInCurrentPage(ToIntFunction<ShortcutAndWidgetContainer> rankProvider) {
if (getChildCount() < 1) {
return null;
}
- ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets();
- int lastRank = currContainer.getChildCount() - 1;
+ ShortcutAndWidgetContainer container = getCurrentCellLayout().getShortcutsAndWidgets();
+ int rank = rankProvider.applyAsInt(container);
if (mGridCountX > 0) {
- return currContainer.getChildAt(lastRank % mGridCountX, lastRank / mGridCountX);
+ return container.getChildAt(rank % mGridCountX, rank / mGridCountX);
} else {
- return currContainer.getChildAt(lastRank);
+ return container.getChildAt(rank);
}
}
@@ -517,7 +455,7 @@
}
public boolean rankOnCurrentPage(int rank) {
- int p = rank / mMaxItemsPerPage;
+ int p = rank / mOrganizer.getMaxItemsPerPage();
return p == getNextPage();
}
@@ -563,15 +501,16 @@
// Animation only happens on the current page.
int pageToAnimate = getNextPage();
+ int maxItemsPerPage = mOrganizer.getMaxItemsPerPage();
- int pageT = target / mMaxItemsPerPage;
- int pagePosT = target % mMaxItemsPerPage;
+ int pageT = target / maxItemsPerPage;
+ int pagePosT = target % maxItemsPerPage;
if (pageT != pageToAnimate) {
Log.e(TAG, "Cannot animate when the target cell is invisible");
}
- int pagePosE = empty % mMaxItemsPerPage;
- int pageE = empty / mMaxItemsPerPage;
+ int pagePosE = empty % maxItemsPerPage;
+ int pageE = empty / maxItemsPerPage;
int startPos, endPos;
int moveStart, moveEnd;
@@ -588,7 +527,7 @@
if (pageE < pageToAnimate) {
moveStart = empty;
// Instantly move the first item in the current page.
- moveEnd = pageToAnimate * mMaxItemsPerPage;
+ moveEnd = pageToAnimate * maxItemsPerPage;
// Animate the 2nd item in the current page, as the first item was already moved to
// the last page.
startPos = 0;
@@ -606,10 +545,10 @@
// Move the items immediately.
moveStart = empty;
// Instantly move the last item in the current page.
- moveEnd = (pageToAnimate + 1) * mMaxItemsPerPage - 1;
+ moveEnd = (pageToAnimate + 1) * maxItemsPerPage - 1;
// Animations start with the second last item in the page
- startPos = mMaxItemsPerPage - 1;
+ startPos = maxItemsPerPage - 1;
} else {
moveStart = moveEnd = -1;
startPos = pagePosE;
@@ -621,8 +560,8 @@
// Instant moving views.
while (moveStart != moveEnd) {
int rankToMove = moveStart + direction;
- int p = rankToMove / mMaxItemsPerPage;
- int pagePos = rankToMove % mMaxItemsPerPage;
+ int p = rankToMove / maxItemsPerPage;
+ int pagePos = rankToMove % maxItemsPerPage;
int x = pagePos % mGridCountX;
int y = pagePos / mGridCountX;
@@ -667,9 +606,6 @@
for (int i = startPos; i != endPos; i += direction) {
int nextPos = i + direction;
View v = page.getChildAt(nextPos % mGridCountX, nextPos / mGridCountX);
- if (v != null) {
- ((ItemInfo) v.getTag()).rank -= direction;
- }
if (page.animateChildToPosition(v, i % mGridCountX, i / mGridCountX,
REORDER_ANIMATION_DURATION, delay, true, true)) {
delay += delayAmount;
@@ -679,6 +615,6 @@
}
public int itemsPerPage() {
- return mMaxItemsPerPage;
+ return mOrganizer.getMaxItemsPerPage();
}
}
diff --git a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
index c818462..caf6e55 100644
--- a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
+++ b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
@@ -17,6 +17,8 @@
import android.graphics.drawable.Drawable;
+import com.android.launcher3.WorkspaceItemInfo;
+
/**
* Manages the parameters used to draw a Folder preview item.
*/
@@ -25,9 +27,10 @@
float transY;
float scale;
float overlayAlpha;
- FolderPreviewItemAnim anim;
+ public FolderPreviewItemAnim anim;
public boolean hidden;
- Drawable drawable;
+ public Drawable drawable;
+ public WorkspaceItemInfo item;
PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) {
this.transX = transX;
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 49763ba..2d817e6 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -23,28 +23,51 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
+import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.util.FloatProperty;
import android.view.View;
import android.widget.TextView;
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.WorkspaceItemInfo;
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.graphics.PreloadIconDrawable;
import java.util.ArrayList;
import java.util.List;
-
-import androidx.annotation.NonNull;
+import java.util.function.Predicate;
/**
* Manages the drawing and animations of {@link PreviewItemDrawingParams} for a {@link FolderIcon}.
*/
public class PreviewItemManager {
- private FolderIcon mIcon;
+ private static final FloatProperty<PreviewItemManager> CURRENT_PAGE_ITEMS_TRANS_X =
+ new FloatProperty<PreviewItemManager>("currentPageItemsTransX") {
+ @Override
+ public void setValue(PreviewItemManager manager, float v) {
+ manager.mCurrentPageItemsTransX = v;
+ manager.onParamsChanged();
+ }
+
+ @Override
+ public Float get(PreviewItemManager manager) {
+ return manager.mCurrentPageItemsTransX;
+ }
+ };
+
+ private final Context mContext;
+ private final FolderIcon mIcon;
+ private final DrawableFactory mDrawableFactory;
+ private final int mIconSize;
// These variables are all associated with the drawing of the preview; they are stored
// as member variables for shared usage and to avoid computation on each frame
@@ -69,7 +92,10 @@
private static final int ITEM_SLIDE_IN_OUT_DISTANCE_PX = 200;
public PreviewItemManager(FolderIcon icon) {
+ mContext = icon.getContext();
mIcon = icon;
+ mDrawableFactory = DrawableFactory.INSTANCE.get(mContext);
+ mIconSize = Launcher.getLauncher(mContext).getDeviceProfile().folderChildIconSizePx;
}
/**
@@ -200,7 +226,7 @@
}
void buildParamsForPage(int page, ArrayList<PreviewItemDrawingParams> params, boolean animate) {
- List<BubbleTextView> items = mIcon.getPreviewItemsOnPage(page);
+ List<WorkspaceItemInfo> items = mIcon.getPreviewItemsOnPage(page);
int prevNumItems = params.size();
// We adjust the size of the list to match the number of items in the preview.
@@ -214,13 +240,7 @@
int numItemsInFirstPagePreview = page == 0 ? items.size() : MAX_NUM_ITEMS_IN_PREVIEW;
for (int i = 0; i < params.size(); i++) {
PreviewItemDrawingParams p = params.get(i);
- p.drawable = items.get(i).getCompoundDrawables()[1];
-
- if (p.drawable != null && !mIcon.mFolder.isOpen()) {
- // Set the callback to FolderIcon as it is responsible to drawing the icon. The
- // callback will be released when the folder is opened.
- p.drawable.setCallback(mIcon);
- }
+ setDrawable(p, items.get(i));
if (!animate) {
computePreviewItemDrawingParams(i, numItemsInFirstPagePreview, p);
@@ -253,14 +273,8 @@
buildParamsForPage(currentPage, mCurrentPageParams, false);
onParamsChanged();
- ValueAnimator slideAnimator = ValueAnimator.ofFloat(0, ITEM_SLIDE_IN_OUT_DISTANCE_PX);
- slideAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- mCurrentPageItemsTransX = (float) valueAnimator.getAnimatedValue();
- onParamsChanged();
- }
- });
+ ValueAnimator slideAnimator = ObjectAnimator
+ .ofFloat(this, CURRENT_PAGE_ITEMS_TRANS_X, 0, ITEM_SLIDE_IN_OUT_DISTANCE_PX);
slideAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -277,6 +291,25 @@
buildParamsForPage(0, mFirstPageParams, animate);
}
+ void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) {
+ boolean modified = false;
+ for (PreviewItemDrawingParams param : mFirstPageParams) {
+ if (itemCheck.test(param.item)) {
+ setDrawable(param, param.item);
+ modified = true;
+ }
+ }
+ for (PreviewItemDrawingParams param : mCurrentPageParams) {
+ if (itemCheck.test(param.item)) {
+ setDrawable(param, param.item);
+ modified = true;
+ }
+ }
+ if (modified) {
+ mIcon.invalidate();
+ }
+ }
+
boolean verifyDrawable(@NonNull Drawable who) {
for (int i = 0; i < mFirstPageParams.size(); i++) {
if (mFirstPageParams.get(i).drawable == who) {
@@ -296,46 +329,46 @@
* - Moving into a new position
* - Moving out of the preview
*
- * @param oldParams The list of items in the old preview.
- * @param newParams The list of items in the new preview.
+ * @param oldItems The list of items in the old preview.
+ * @param newItems The list of items in the new preview.
* @param dropped The item that was dropped onto the FolderIcon.
*/
- public void onDrop(List<BubbleTextView> oldParams, List<BubbleTextView> newParams,
+ public void onDrop(List<WorkspaceItemInfo> oldItems, List<WorkspaceItemInfo> newItems,
WorkspaceItemInfo dropped) {
- int numItems = newParams.size();
+ int numItems = newItems.size();
final ArrayList<PreviewItemDrawingParams> params = mFirstPageParams;
buildParamsForPage(0, params, false);
// New preview items for items that are moving in (except for the dropped item).
- List<BubbleTextView> moveIn = new ArrayList<>();
- for (BubbleTextView btv : newParams) {
- if (!oldParams.contains(btv) && !btv.getTag().equals(dropped)) {
- moveIn.add(btv);
+ List<WorkspaceItemInfo> moveIn = new ArrayList<>();
+ for (WorkspaceItemInfo newItem : newItems) {
+ if (!oldItems.contains(newItem) && !newItem.equals(dropped)) {
+ moveIn.add(newItem);
}
}
for (int i = 0; i < moveIn.size(); ++i) {
- int prevIndex = newParams.indexOf(moveIn.get(i));
+ int prevIndex = newItems.indexOf(moveIn.get(i));
PreviewItemDrawingParams p = params.get(prevIndex);
computePreviewItemDrawingParams(prevIndex, numItems, p);
- updateTransitionParam(p, moveIn.get(i), ENTER_INDEX, newParams.indexOf(moveIn.get(i)),
+ updateTransitionParam(p, moveIn.get(i), ENTER_INDEX, newItems.indexOf(moveIn.get(i)),
numItems);
}
// Items that are moving into new positions within the preview.
- for (int newIndex = 0; newIndex < newParams.size(); ++newIndex) {
- int oldIndex = oldParams.indexOf(newParams.get(newIndex));
+ for (int newIndex = 0; newIndex < newItems.size(); ++newIndex) {
+ int oldIndex = oldItems.indexOf(newItems.get(newIndex));
if (oldIndex >= 0 && newIndex != oldIndex) {
PreviewItemDrawingParams p = params.get(newIndex);
- updateTransitionParam(p, newParams.get(newIndex), oldIndex, newIndex, numItems);
+ updateTransitionParam(p, newItems.get(newIndex), oldIndex, newIndex, numItems);
}
}
// Old preview items that need to be moved out.
- List<BubbleTextView> moveOut = new ArrayList<>(oldParams);
- moveOut.removeAll(newParams);
+ List<WorkspaceItemInfo> moveOut = new ArrayList<>(oldItems);
+ moveOut.removeAll(newItems);
for (int i = 0; i < moveOut.size(); ++i) {
- BubbleTextView item = moveOut.get(i);
- int oldIndex = oldParams.indexOf(item);
+ WorkspaceItemInfo item = moveOut.get(i);
+ int oldIndex = oldItems.indexOf(item);
PreviewItemDrawingParams p = computePreviewItemDrawingParams(oldIndex, numItems, null);
updateTransitionParam(p, item, oldIndex, EXIT_INDEX, numItems);
params.add(0, p); // We want these items first so that they are on drawn last.
@@ -348,14 +381,9 @@
}
}
- private void updateTransitionParam(final PreviewItemDrawingParams p, BubbleTextView btv,
+ private void updateTransitionParam(final PreviewItemDrawingParams p, WorkspaceItemInfo item,
int prevIndex, int newIndex, int numItems) {
- p.drawable = btv.getCompoundDrawables()[1];
- if (!mIcon.mFolder.isOpen()) {
- // Set the callback to FolderIcon as it is responsible to drawing the icon. The
- // callback will be released when the folder is opened.
- p.drawable.setCallback(mIcon);
- }
+ setDrawable(p, item);
FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, prevIndex, numItems,
newIndex, numItems, DROP_IN_ANIMATION_DURATION, null);
@@ -364,4 +392,20 @@
}
p.anim = anim;
}
+
+ private void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) {
+ if (item.hasPromiseIconUi()) {
+ PreloadIconDrawable drawable = mDrawableFactory.newPendingIcon(mContext, item);
+ drawable.setLevel(item.getInstallProgress());
+ p.drawable = drawable;
+ } else {
+ p.drawable = mDrawableFactory.newIcon(mContext, item);
+ }
+ p.drawable.setBounds(0, 0, mIconSize, mIconSize);
+ p.item = item;
+
+ // Set the callback to FolderIcon as it is responsible to drawing the icon. The
+ // callback will be released when the folder is opened.
+ p.drawable.setCallback(mIcon);
+ }
}
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 7eb4015..f579451 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -16,6 +16,8 @@
package com.android.launcher3.graphics;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
@@ -25,16 +27,15 @@
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Handler;
import android.view.View;
import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.icons.BitmapRenderer;
-import com.android.launcher3.util.UiThreadHelper;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.PendingAppWidgetHostView;
@@ -87,6 +88,9 @@
Rect bounds = getDrawableBounds(d);
destCanvas.translate(blurSizeOutline / 2 - bounds.left,
blurSizeOutline / 2 - bounds.top);
+ if (d instanceof FastBitmapDrawable) {
+ ((FastBitmapDrawable) d).setScale(1);
+ }
d.draw(destCanvas);
} else {
final Rect clipRect = mTempRect;
@@ -153,7 +157,7 @@
}
mOutlineGeneratorCallback = new OutlineGeneratorCallback(preview);
- new Handler(UiThreadHelper.getBackgroundLooper()).post(mOutlineGeneratorCallback);
+ UI_HELPER_EXECUTOR.post(mOutlineGeneratorCallback);
}
protected static Rect getDrawableBounds(Drawable d) {
@@ -191,15 +195,22 @@
private final Bitmap mPreviewSnapshot;
private final Context mContext;
+ private final boolean mIsIcon;
OutlineGeneratorCallback(Bitmap preview) {
mPreviewSnapshot = preview;
mContext = mView.getContext();
+ mIsIcon = mView instanceof BubbleTextView;
}
@Override
public void run() {
Bitmap preview = convertPreviewToAlphaBitmap(mPreviewSnapshot);
+ if (mIsIcon) {
+ int size = Launcher.getLauncher(mContext).getDeviceProfile().iconSizePx;
+ preview = Bitmap.createScaledBitmap(preview, size, size, false);
+ }
+ //else case covers AppWidgetHost (doesn't drag/drop across different device profiles)
// We start by removing most of the alpha channel so as to ignore shadows, and
// other types of partial transparency when defining the shape of the object
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
index c9566cb..837301f 100644
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -17,6 +17,7 @@
package com.android.launcher3.graphics;
import static com.android.launcher3.graphics.IconShape.getShapePath;
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -31,6 +32,8 @@
import android.os.UserHandle;
import android.util.ArrayMap;
+import androidx.annotation.UiThread;
+
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.R;
@@ -38,16 +41,13 @@
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.ResourceBasedOverride;
-import androidx.annotation.UiThread;
-
/**
* Factory for creating new drawables.
*/
public class DrawableFactory implements ResourceBasedOverride {
public static final MainThreadInitializedObject<DrawableFactory> INSTANCE =
- new MainThreadInitializedObject<>(c -> Overrides.getObject(DrawableFactory.class,
- c.getApplicationContext(), R.string.drawable_factory_class));
+ forOverride(DrawableFactory.class, R.string.drawable_factory_class);
protected final UserHandle mMyUser = Process.myUserHandle();
protected final ArrayMap<UserHandle, Bitmap> mUserBadges = new ArrayMap<>();
@@ -80,26 +80,25 @@
* Returns a drawable that can be used as a badge for the user or null.
*/
@UiThread
- public Drawable getBadgeForUser(UserHandle user, Context context) {
+ public Drawable getBadgeForUser(UserHandle user, Context context, int badgeSize) {
if (mMyUser.equals(user)) {
return null;
}
- Bitmap badgeBitmap = getUserBadge(user, context);
+ Bitmap badgeBitmap = getUserBadge(user, context, badgeSize);
FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap);
d.setFilterBitmap(true);
d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight());
return d;
}
- protected synchronized Bitmap getUserBadge(UserHandle user, Context context) {
+ protected synchronized Bitmap getUserBadge(UserHandle user, Context context, int badgeSize) {
Bitmap badgeBitmap = mUserBadges.get(user);
if (badgeBitmap != null) {
return badgeBitmap;
}
final Resources res = context.getApplicationContext().getResources();
- int badgeSize = res.getDimensionPixelSize(R.dimen.profile_badge_size);
badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888);
Drawable drawable = context.getPackageManager().getUserBadgedDrawableForDensity(
diff --git a/src/com/android/launcher3/graphics/GridOptionsProvider.java b/src/com/android/launcher3/graphics/GridOptionsProvider.java
index efd39ee..71b4366 100644
--- a/src/com/android/launcher3/graphics/GridOptionsProvider.java
+++ b/src/com/android/launcher3/graphics/GridOptionsProvider.java
@@ -1,5 +1,7 @@
package com.android.launcher3.graphics;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.res.XmlResourceParser;
@@ -17,8 +19,6 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.GridOption;
import com.android.launcher3.R;
-import com.android.launcher3.util.LooperExecutor;
-import com.android.launcher3.util.UiThreadHelper;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -180,10 +180,10 @@
throw new FileNotFoundException(e.getMessage());
}
- LooperExecutor executor = new LooperExecutor(UiThreadHelper.getBackgroundLooper());
try {
return openPipeHelper(uri, MIME_TYPE_PNG, null,
- executor.submit(new LauncherPreviewRenderer(getContext(), idp)), BITMAP_WRITER);
+ UI_HELPER_EXECUTOR.submit(new LauncherPreviewRenderer(getContext(), idp)),
+ BITMAP_WRITER);
} catch (Exception e) {
throw new FileNotFoundException(e.getMessage());
}
diff --git a/src/com/android/launcher3/graphics/OverviewScrim.java b/src/com/android/launcher3/graphics/OverviewScrim.java
new file mode 100644
index 0000000..d707403
--- /dev/null
+++ b/src/com/android/launcher3/graphics/OverviewScrim.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 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.graphics;
+
+import static android.view.View.VISIBLE;
+
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * View scrim which draws behind overview (recent apps).
+ */
+public class OverviewScrim extends Scrim {
+
+ private @NonNull View mStableScrimmedView;
+ // Might be higher up if mStableScrimmedView is invisible.
+ private @Nullable View mCurrentScrimmedView;
+
+ public OverviewScrim(View view) {
+ super(view);
+ mStableScrimmedView = mCurrentScrimmedView = mLauncher.getOverviewPanel();
+
+ onExtractedColorsChanged(mWallpaperColorInfo);
+ }
+
+ public void onInsetsChanged(Rect insets) {
+ mStableScrimmedView = (OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0
+ ? mLauncher.getHotseat()
+ : mLauncher.getOverviewPanel();
+ }
+
+ public void updateCurrentScrimmedView(ViewGroup root) {
+ // Find the lowest view that is at or above the view we want to show the scrim behind.
+ mCurrentScrimmedView = mStableScrimmedView;
+ int currentIndex = root.indexOfChild(mCurrentScrimmedView);
+ final int childCount = root.getChildCount();
+ while (mCurrentScrimmedView.getVisibility() != VISIBLE && currentIndex < childCount) {
+ currentIndex++;
+ mCurrentScrimmedView = root.getChildAt(currentIndex);
+ }
+ }
+
+ /**
+ * @return The view to draw the scrim behind.
+ */
+ public View getScrimmedView() {
+ return mCurrentScrimmedView;
+ }
+}
diff --git a/src/com/android/launcher3/graphics/Scrim.java b/src/com/android/launcher3/graphics/Scrim.java
new file mode 100644
index 0000000..5c14f8d
--- /dev/null
+++ b/src/com/android/launcher3/graphics/Scrim.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 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.graphics;
+
+import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
+
+import android.graphics.Canvas;
+import android.util.Property;
+import android.view.View;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.uioverrides.WallpaperColorInfo;
+
+/**
+ * Contains general scrim properties such as wallpaper-extracted color that subclasses can use.
+ */
+public class Scrim implements View.OnAttachStateChangeListener,
+ WallpaperColorInfo.OnChangeListener {
+
+ public static Property<Scrim, Float> SCRIM_PROGRESS =
+ new Property<Scrim, Float>(Float.TYPE, "scrimProgress") {
+ @Override
+ public Float get(Scrim scrim) {
+ return scrim.mScrimProgress;
+ }
+
+ @Override
+ public void set(Scrim scrim, Float value) {
+ scrim.setScrimProgress(value);
+ }
+ };
+
+ protected final Launcher mLauncher;
+ protected final WallpaperColorInfo mWallpaperColorInfo;
+ protected final View mRoot;
+
+ protected float mScrimProgress;
+ protected int mScrimColor;
+ protected int mScrimAlpha = 0;
+
+ public Scrim(View view) {
+ mRoot = view;
+ mLauncher = Launcher.getLauncher(view.getContext());
+ mWallpaperColorInfo = WallpaperColorInfo.getInstance(mLauncher);
+
+ view.addOnAttachStateChangeListener(this);
+ }
+
+ public void draw(Canvas canvas) {
+ canvas.drawColor(setColorAlphaBound(mScrimColor, mScrimAlpha));
+ }
+
+ private void setScrimProgress(float progress) {
+ if (mScrimProgress != progress) {
+ mScrimProgress = progress;
+ mScrimAlpha = Math.round(255 * mScrimProgress);
+ invalidate();
+ }
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ mWallpaperColorInfo.addOnChangeListener(this);
+ onExtractedColorsChanged(mWallpaperColorInfo);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ mWallpaperColorInfo.removeOnChangeListener(this);
+ }
+
+ @Override
+ public void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo) {
+ mScrimColor = wallpaperColorInfo.getMainColor();
+ if (mScrimAlpha > 0) {
+ invalidate();
+ }
+ }
+
+ public void invalidate() {
+ mRoot.invalidate();
+ }
+}
diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
index c0aa75f..15ff207 100644
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
@@ -40,34 +40,19 @@
import android.util.Property;
import android.view.View;
+import androidx.core.graphics.ColorUtils;
+
import com.android.launcher3.CellLayout;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.Workspace;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
import com.android.launcher3.util.Themes;
-import androidx.core.graphics.ColorUtils;
-
/**
* View scrim which draws behind hotseat and workspace
*/
-public class WorkspaceAndHotseatScrim implements
- View.OnAttachStateChangeListener, WallpaperColorInfo.OnChangeListener {
-
- public static Property<WorkspaceAndHotseatScrim, Float> SCRIM_PROGRESS =
- new Property<WorkspaceAndHotseatScrim, Float>(Float.TYPE, "scrimProgress") {
- @Override
- public Float get(WorkspaceAndHotseatScrim scrim) {
- return scrim.mScrimProgress;
- }
-
- @Override
- public void set(WorkspaceAndHotseatScrim scrim, Float value) {
- scrim.setScrimProgress(value);
- }
- };
+public class WorkspaceAndHotseatScrim extends Scrim {
public static Property<WorkspaceAndHotseatScrim, Float> SYSUI_PROGRESS =
new Property<WorkspaceAndHotseatScrim, Float>(Float.TYPE, "sysUiProgress") {
@@ -117,9 +102,6 @@
private static final int ALPHA_MASK_WIDTH_DP = 2;
private final Rect mHighlightRect = new Rect();
- private final Launcher mLauncher;
- private final WallpaperColorInfo mWallpaperColorInfo;
- private final View mRoot;
private Workspace mWorkspace;
@@ -132,11 +114,6 @@
private final Drawable mTopScrim;
- private int mFullScrimColor;
-
- private float mScrimProgress;
- private int mScrimAlpha = 0;
-
private float mSysUiProgress = 1;
private boolean mHideSysUiScrim;
@@ -144,9 +121,7 @@
private float mSysUiAnimMultiplier = 1;
public WorkspaceAndHotseatScrim(View view) {
- mRoot = view;
- mLauncher = Launcher.getLauncher(view.getContext());
- mWallpaperColorInfo = WallpaperColorInfo.getInstance(mLauncher);
+ super(view);
mMaskHeight = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_DP,
view.getResources().getDisplayMetrics());
@@ -154,7 +129,6 @@
mBottomMask = mTopScrim == null ? null : createDitheredAlphaMask();
mHideSysUiScrim = mTopScrim == null;
- view.addOnAttachStateChangeListener(this);
onExtractedColorsChanged(mWallpaperColorInfo);
}
@@ -176,7 +150,7 @@
canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
}
- canvas.drawColor(setColorAlphaBound(mFullScrimColor, mScrimAlpha));
+ super.draw(canvas);
canvas.restore();
}
@@ -190,11 +164,8 @@
mSysUiAnimMultiplier = 0;
reapplySysUiAlphaNoInvalidate();
- ObjectAnimator anim = ObjectAnimator.ofFloat(this, SYSUI_ANIM_MULTIPLIER, 1);
- anim.setAutoCancel(true);
- anim.setDuration(600);
- anim.setStartDelay(mLauncher.getWindow().getTransitionBackgroundFadeDuration());
- anim.start();
+ animateToSysuiMultiplier(1, 600,
+ mLauncher.getWindow().getTransitionBackgroundFadeDuration());
mAnimateScrimOnNextDraw = false;
}
@@ -207,24 +178,27 @@
}
}
- public void onInsetsChanged(Rect insets) {
- mDrawTopScrim = mTopScrim != null && insets.top > 0;
- mDrawBottomScrim = mBottomMask != null &&
- !mLauncher.getDeviceProfile().isVerticalBarLayout();
+ public void animateToSysuiMultiplier(float toMultiplier, long duration,
+ long startDelay) {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(this, SYSUI_ANIM_MULTIPLIER, toMultiplier);
+ anim.setAutoCancel(true);
+ anim.setDuration(duration);
+ anim.setStartDelay(startDelay);
+ anim.start();
}
- private void setScrimProgress(float progress) {
- if (mScrimProgress != progress) {
- mScrimProgress = progress;
- mScrimAlpha = Math.round(255 * mScrimProgress);
- invalidate();
- }
+ /**
+ * Determines whether to draw the top and/or bottom scrim based on new insets.
+ */
+ public void onInsetsChanged(Rect insets, boolean allowSysuiScrims) {
+ mDrawTopScrim = allowSysuiScrims && mTopScrim != null && insets.top > 0;
+ mDrawBottomScrim = allowSysuiScrims && mBottomMask != null
+ && !mLauncher.getDeviceProfile().isVerticalBarLayout();
}
@Override
public void onViewAttachedToWindow(View view) {
- mWallpaperColorInfo.addOnChangeListener(this);
- onExtractedColorsChanged(mWallpaperColorInfo);
+ super.onViewAttachedToWindow(view);
if (mTopScrim != null) {
IntentFilter filter = new IntentFilter(ACTION_SCREEN_OFF);
@@ -235,7 +209,7 @@
@Override
public void onViewDetachedFromWindow(View view) {
- mWallpaperColorInfo.removeOnChangeListener(this);
+ super.onViewDetachedFromWindow(view);
if (mTopScrim != null) {
mRoot.getContext().unregisterReceiver(mReceiver);
}
@@ -248,10 +222,7 @@
mBottomMaskPaint.setColor(ColorUtils.compositeColors(DARK_SCRIM_COLOR,
wallpaperColorInfo.getMainColor()));
reapplySysUiAlpha();
- mFullScrimColor = wallpaperColorInfo.getMainColor();
- if (mScrimAlpha > 0) {
- invalidate();
- }
+ super.onExtractedColorsChanged(wallpaperColorInfo);
}
public void setSize(int w, int h) {
@@ -291,10 +262,6 @@
}
}
- public void invalidate() {
- mRoot.invalidate();
- }
-
public Bitmap createDitheredAlphaMask() {
DisplayMetrics dm = mLauncher.getResources().getDisplayMetrics();
int width = ResourceUtils.pxFromDp(ALPHA_MASK_WIDTH_DP, dm);
diff --git a/src/com/android/launcher3/icons/ComponentWithLabel.java b/src/com/android/launcher3/icons/ComponentWithLabel.java
index 46b5002..832956d 100644
--- a/src/com/android/launcher3/icons/ComponentWithLabel.java
+++ b/src/com/android/launcher3/icons/ComponentWithLabel.java
@@ -34,9 +34,11 @@
class ComponentCachingLogic implements CachingLogic<ComponentWithLabel> {
private final PackageManager mPackageManager;
+ private final boolean mAddToMemCache;
- public ComponentCachingLogic(Context context) {
+ public ComponentCachingLogic(Context context, boolean addToMemCache) {
mPackageManager = context.getPackageManager();
+ mAddToMemCache = addToMemCache;
}
@Override
@@ -60,5 +62,10 @@
// Do not load icon.
target.icon = BitmapInfo.LOW_RES_ICON;
}
+
+ @Override
+ public boolean addToMemCache() {
+ return mAddToMemCache;
+ }
}
}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 648445e..9c46260 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -16,6 +16,9 @@
package com.android.launcher3.icons;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -29,17 +32,18 @@
import android.os.UserHandle;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.AppInfo;
import com.android.launcher3.IconProvider;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.LauncherFiles;
-import com.android.launcher3.LauncherModel;
-import com.android.launcher3.MainThreadExecutor;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
import com.android.launcher3.icons.cache.BaseIconCache;
import com.android.launcher3.icons.cache.CachingLogic;
@@ -50,8 +54,6 @@
import java.util.function.Supplier;
-import androidx.annotation.NonNull;
-
/**
* Cache of application icons. Icons can be made from any thread.
*/
@@ -59,8 +61,6 @@
private static final String TAG = "Launcher.IconCache";
- private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
-
private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic;
private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic;
@@ -72,14 +72,14 @@
private int mPendingIconRequestCount = 0;
public IconCache(Context context, InvariantDeviceProfile inv) {
- super(context, LauncherFiles.APP_ICONS_DB, LauncherModel.getWorkerLooper(),
+ super(context, LauncherFiles.APP_ICONS_DB, MODEL_EXECUTOR.getLooper(),
inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */);
- mComponentWithLabelCachingLogic = new ComponentCachingLogic(context);
- mLauncherActivityInfoCachingLogic = new LauncherActivtiyCachingLogic(this);
+ mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false);
+ mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context);
mLauncherApps = LauncherAppsCompat.getInstance(mContext);
mUserManager = UserManagerCompat.getInstance(mContext);
mInstantAppResolver = InstantAppResolver.newInstance(mContext);
- mIconProvider = IconProvider.newInstance(context);
+ mIconProvider = IconProvider.INSTANCE.get(context);
}
@Override
@@ -123,7 +123,7 @@
final ItemInfoWithIcon info) {
Preconditions.assertUIThread();
if (mPendingIconRequestCount <= 0) {
- LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_FOREGROUND);
+ MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
}
mPendingIconRequestCount ++;
@@ -135,7 +135,7 @@
} else if (info instanceof PackageItemInfo) {
getTitleAndIconForApp((PackageItemInfo) info, false);
}
- mMainThreadExecutor.execute(() -> {
+ MAIN_EXECUTOR.execute(() -> {
caller.reapplyItemInfo(info);
onEnd();
});
@@ -148,7 +148,7 @@
private void onIconRequestEnd() {
mPendingIconRequestCount --;
if (mPendingIconRequestCount <= 0) {
- LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
}
@@ -194,7 +194,7 @@
public synchronized String getTitleNoCache(ComponentWithLabel info) {
CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info,
mComponentWithLabelCachingLogic, false /* usePackageIcon */,
- true /* useLowResIcon */, false /* addToMemCache */);
+ true /* useLowResIcon */);
return Utilities.trim(entry.title);
}
@@ -237,7 +237,8 @@
@Override
protected String getIconSystemState(String packageName) {
- return mIconProvider.getSystemStateForPackage(mSystemState, packageName);
+ return mIconProvider.getSystemStateForPackage(mSystemState, packageName)
+ + ",flags_asi:" + FeatureFlags.APP_SEARCH_IMPROVEMENTS.get();
}
public static abstract class IconLoadRequest extends HandlerRunnable {
diff --git a/src/com/android/launcher3/icons/LauncherActivtiyCachingLogic.java b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
similarity index 67%
rename from src/com/android/launcher3/icons/LauncherActivtiyCachingLogic.java
rename to src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
index 7c99633..f9a94da 100644
--- a/src/com/android/launcher3/icons/LauncherActivtiyCachingLogic.java
+++ b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
@@ -20,14 +20,23 @@
import android.content.pm.LauncherActivityInfo;
import android.os.UserHandle;
+import com.android.launcher3.IconProvider;
+import com.android.launcher3.R;
import com.android.launcher3.icons.cache.CachingLogic;
+import com.android.launcher3.util.ResourceBasedOverride;
-public class LauncherActivtiyCachingLogic implements CachingLogic<LauncherActivityInfo> {
+/**
+ * Caching logic for LauncherActivityInfo.
+ */
+public class LauncherActivityCachingLogic
+ implements CachingLogic<LauncherActivityInfo>, ResourceBasedOverride {
- private final IconCache mCache;
-
- public LauncherActivtiyCachingLogic(IconCache cache) {
- mCache = cache;
+ /**
+ * Creates and returns a new instance
+ */
+ public static LauncherActivityCachingLogic newInstance(Context context) {
+ return Overrides.getObject(LauncherActivityCachingLogic.class, context,
+ R.string.launcher_activity_logic_class);
}
@Override
@@ -49,8 +58,10 @@
public void loadIcon(Context context, LauncherActivityInfo object,
BitmapInfo target) {
LauncherIcons li = LauncherIcons.obtain(context);
- li.createBadgedIconBitmap(mCache.getFullResIcon(object),
+ li.createBadgedIconBitmap(
+ IconProvider.INSTANCE.get(context)
+ .getIcon(object, li.mFillResIconDpi, true /* flattenDrawable */),
object.getUser(), object.getApplicationInfo().targetSdkVersion).applyTo(target);
li.recycle();
}
-}
\ No newline at end of file
+}
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index 7632408..adc92c4 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -24,6 +24,8 @@
import android.graphics.drawable.Drawable;
import android.os.Process;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.AppInfo;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.InvariantDeviceProfile;
@@ -37,8 +39,6 @@
import java.util.function.Supplier;
-import androidx.annotation.Nullable;
-
/**
* Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
* that are threadsafe.
diff --git a/src/com/android/launcher3/logging/DumpTargetWrapper.java b/src/com/android/launcher3/logging/DumpTargetWrapper.java
index 365e8f2..067bdfd 100644
--- a/src/com/android/launcher3/logging/DumpTargetWrapper.java
+++ b/src/com/android/launcher3/logging/DumpTargetWrapper.java
@@ -15,17 +15,22 @@
*/
package com.android.launcher3.logging;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+
+import android.content.ComponentName;
import android.os.Process;
import android.text.TextUtils;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.model.nano.LauncherDumpProto;
import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType;
import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget;
import com.android.launcher3.model.nano.LauncherDumpProto.ItemType;
import com.android.launcher3.model.nano.LauncherDumpProto.UserType;
+import com.android.launcher3.util.ShortcutUtil;
import java.util.ArrayList;
import java.util.List;
@@ -73,20 +78,23 @@
public DumpTarget newItemTarget(ItemInfo info) {
DumpTarget dt = new DumpTarget();
dt.type = DumpTarget.Type.ITEM;
-
+ if (info == null) {
+ return dt;
+ }
switch (info.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
dt.itemType = ItemType.APP_ICON;
break;
- case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
- dt.itemType = ItemType.UNKNOWN_ITEMTYPE;
- break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
dt.itemType = ItemType.WIDGET;
break;
- case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+ case ITEM_TYPE_DEEP_SHORTCUT:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
dt.itemType = ItemType.SHORTCUT;
break;
+ default:
+ dt.itemType = ItemType.UNKNOWN_ITEMTYPE;
+ break;
}
return dt;
}
@@ -120,6 +128,9 @@
}
private static String getItemStr(DumpTarget t) {
+ if (t == null) {
+ return "";
+ }
String typeStr = LoggerUtils.getFieldName(t.itemType, ItemType.class);
if (!TextUtils.isEmpty(t.packageName)) {
typeStr += ", package=" + t.packageName;
@@ -132,8 +143,15 @@
}
public DumpTarget writeToDumpTarget(ItemInfo info) {
- node.component = info.getTargetComponent() == null? "":
- info.getTargetComponent().flattenToString();
+ if (info == null) {
+ return node;
+ }
+ if (ShortcutUtil.isDeepShortcut(info)) {
+ node.component = ((WorkspaceItemInfo) info).getDeepShortcutId();
+ } else {
+ ComponentName cmp = info.getTargetComponent();
+ node.component = cmp == null ? "" : cmp.flattenToString();
+ }
node.packageName = info.getTargetComponent() == null? "":
info.getTargetComponent().getPackageName();
if (info instanceof LauncherAppWidgetInfo) {
diff --git a/src/com/android/launcher3/logging/EventLogArray.java b/src/com/android/launcher3/logging/EventLogArray.java
index f20f365..3ecfb23 100644
--- a/src/com/android/launcher3/logging/EventLogArray.java
+++ b/src/com/android/launcher3/logging/EventLogArray.java
@@ -16,11 +16,13 @@
package com.android.launcher3.logging;
+import android.util.Log;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
+import java.util.Random;
/**
* A utility class to record and log events. Events are stored in a fixed size array and old logs
@@ -37,6 +39,7 @@
private final String name;
private final EventEntry[] logs;
private int nextIndex;
+ private int mLogId;
public EventLogArray(String name, int size) {
this.name = name;
@@ -52,10 +55,6 @@
addLog(TYPE_INTEGER, event, extras);
}
- public void addLog(String event, float extras) {
- addLog(TYPE_FLOAT, event, extras);
- }
-
public void addLog(String event, boolean extras) {
addLog(extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE, event, 0);
}
@@ -65,7 +64,7 @@
int last = (nextIndex + logs.length - 1) % logs.length;
int secondLast = (nextIndex + logs.length - 2) % logs.length;
if (isEntrySame(logs[last], type, event) && isEntrySame(logs[secondLast], type, event)) {
- logs[last].update(type, event, extras);
+ logs[last].update(type, event, extras, mLogId);
logs[secondLast].duplicateCount++;
return;
}
@@ -73,7 +72,7 @@
if (logs[nextIndex] == null) {
logs[nextIndex] = new EventEntry();
}
- logs[nextIndex].update(type, event, extras);
+ logs[nextIndex].update(type, event, extras, mLogId);
nextIndex = (nextIndex + 1) % logs.length;
}
@@ -113,10 +112,18 @@
if (log.duplicateCount > 0) {
msg.append(" & ").append(log.duplicateCount).append(" similar events");
}
+ msg.append(" traceId: ").append(log.traceId);
writer.println(msg);
}
}
+ /** Returns a 3 digit random number between 100-999 */
+ public int generateAndSetLogId() {
+ Random r = new Random();
+ mLogId = r.nextInt(900) + 100;
+ return mLogId;
+ }
+
private boolean isEntrySame(EventEntry entry, int type, String event) {
return entry != null && entry.type == type && entry.event.equals(event);
}
@@ -129,11 +136,13 @@
private float extras;
private long time;
private int duplicateCount;
+ private int traceId;
- public void update(int type, String event, float extras) {
+ public void update(int type, String event, float extras, int traceId) {
this.type = type;
this.event = event;
this.extras = extras;
+ this.traceId = traceId;
time = System.currentTimeMillis();
duplicateCount = 0;
}
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index f7f8ef1..923a89b 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -1,13 +1,14 @@
package com.android.launcher3.logging;
+import static com.android.launcher3.util.Executors.createAndStartNewLooper;
+
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;
import android.util.Pair;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.IOUtils;
import java.io.BufferedReader;
import java.io.File;
@@ -29,8 +30,7 @@
*/
public final class FileLog {
- protected static final boolean ENABLED =
- FeatureFlags.IS_DOGFOOD_BUILD || Utilities.IS_DEBUG_DEVICE;
+ protected static final boolean ENABLED = true;
private static final String FILE_NAME_PREFIX = "log-";
private static final DateFormat DATE_FORMAT =
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
@@ -91,9 +91,8 @@
private static Handler getHandler() {
synchronized (DATE_FORMAT) {
if (sHandler == null) {
- HandlerThread thread = new HandlerThread("file-logger");
- thread.start();
- sHandler = new Handler(thread.getLooper(), new LogWriterCallback());
+ sHandler = new Handler(createAndStartNewLooper("file-logger"),
+ new LogWriterCallback());
}
}
return sHandler;
@@ -131,7 +130,7 @@
private PrintWriter mCurrentWriter = null;
private void closeWriter() {
- Utilities.closeSilently(mCurrentWriter);
+ IOUtils.closeSilently(mCurrentWriter);
mCurrentWriter = null;
}
@@ -219,7 +218,7 @@
} catch (Exception e) {
// ignore
} finally {
- Utilities.closeSilently(in);
+ IOUtils.closeSilently(in);
}
}
}
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index 9b75b43..925b7ba 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -44,6 +44,7 @@
public class LoggerUtils {
private static final ArrayMap<Class, SparseArray<String>> sNameCache = new ArrayMap<>();
private static final String UNKNOWN = "UNKNOWN";
+ private static final int DEFAULT_PREDICTED_RANK = -100;
public static String getFieldName(int value, Class c) {
SparseArray<String> cache;
@@ -90,7 +91,7 @@
}
public static String getTargetStr(Target t) {
- if (t == null){
+ if (t == null) {
return "";
}
String str = "";
@@ -108,7 +109,7 @@
t.containerType == NAVBAR) {
str += " id=" + t.pageIndex;
} else if (t.containerType == ContainerType.FOLDER) {
- str += " grid(" + t.gridX + "," + t.gridY + ")";
+ str += "[PageIndex=" + t.pageIndex + ", grid(" + t.gridX + "," + t.gridY + ")]";
}
break;
default:
@@ -137,17 +138,16 @@
if (t.intentHash != 0) {
typeStr += ", intentHash=" + t.intentHash;
}
- if ((t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0) &&
- t.itemType != ItemType.TASK) {
+ if (t.itemType == ItemType.FOLDER_ICON) {
+ typeStr += ", grid(" + t.gridX + "," + t.gridY + ")";
+ } else if ((t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0)
+ && t.itemType != ItemType.TASK) {
typeStr += ", predictiveRank=" + t.predictedRank + ", grid(" + t.gridX + "," + t.gridY
- + "), span(" + t.spanX + "," + t.spanY
- + "), pageIdx=" + t.pageIndex;
-
+ + "), span(" + t.spanX + "," + t.spanY + "), pageIdx=" + t.pageIndex;
}
if (t.searchQueryLength != 0) {
typeStr += ", searchQueryLength=" + t.searchQueryLength;
}
-
if (t.itemType == ItemType.TASK) {
typeStr += ", pageIdx=" + t.pageIndex;
}
@@ -168,17 +168,17 @@
public static Target newItemTarget(ItemInfo info, InstantAppResolver instantAppResolver) {
Target t = newTarget(Target.Type.ITEM);
-
switch (info.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
t.itemType = (instantAppResolver != null && info instanceof AppInfo
- && instantAppResolver.isInstantApp(((AppInfo) info)) )
+ && instantAppResolver.isInstantApp(((AppInfo) info)))
? ItemType.WEB_APP
: ItemType.APP_ICON;
- t.predictedRank = -100; // Never assigned
+ t.predictedRank = DEFAULT_PREDICTED_RANK;
break;
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
t.itemType = ItemType.SHORTCUT;
+ t.predictedRank = DEFAULT_PREDICTED_RANK;
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
t.itemType = ItemType.FOLDER_ICON;
@@ -188,6 +188,7 @@
break;
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
t.itemType = ItemType.DEEPSHORTCUT;
+ t.predictedRank = DEFAULT_PREDICTED_RANK;
break;
}
return t;
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 9b9543e..cad95b0 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -40,5 +40,7 @@
public void logAppLaunch(View v, Intent intent) { }
public void logTaskLaunch(View v, ComponentKey key) { }
+ public void logTaskDismiss(View v, ComponentKey key) { }
+ public void logSwipeOnContainer(boolean isSwipingToLeft, int pageId) { }
public void verify() {} // TODO: should move into robo tests
}
diff --git a/src/com/android/launcher3/logging/StatsLogUtils.java b/src/com/android/launcher3/logging/StatsLogUtils.java
index 647f255..b02a050 100644
--- a/src/com/android/launcher3/logging/StatsLogUtils.java
+++ b/src/com/android/launcher3/logging/StatsLogUtils.java
@@ -1,9 +1,12 @@
package com.android.launcher3.logging;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.DEFAULT_CONTAINERTYPE;
+
import android.view.View;
import android.view.ViewParent;
import com.android.launcher3.ItemInfo;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import androidx.annotation.Nullable;
@@ -64,4 +67,20 @@
}
return null;
}
+
+ public static int getContainerTypeFromState(int state) {
+ int containerType = DEFAULT_CONTAINERTYPE;
+ switch (state) {
+ case StatsLogUtils.LAUNCHER_STATE_ALLAPPS:
+ containerType = ContainerType.ALLAPPS;
+ break;
+ case StatsLogUtils.LAUNCHER_STATE_HOME:
+ containerType = ContainerType.WORKSPACE;
+ break;
+ case StatsLogUtils.LAUNCHER_STATE_OVERVIEW:
+ containerType = ContainerType.OVERVIEW;
+ break;
+ }
+ return containerType;
+ }
}
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index bd785a1..99906fe 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -26,6 +26,8 @@
import static com.android.launcher3.logging.LoggerUtils.newTarget;
import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
+import static java.util.Optional.ofNullable;
+
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -35,6 +37,7 @@
import android.util.Log;
import android.view.View;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.DropTarget;
@@ -58,7 +61,7 @@
/**
* Manages the creation of {@link LauncherEvent}.
* To debug this class, execute following command before side loading a new apk.
- *
+ * <p>
* $ adb shell setprop log.tag.UserEvent VERBOSE
*/
public class UserEventDispatcher implements ResourceBasedOverride {
@@ -94,19 +97,26 @@
/**
* Fills in the container data on the given event if the given view is not null.
+ *
* @return whether container data was added.
*/
- public static boolean fillInLogContainerData(LauncherLogProto.LauncherEvent event, @Nullable View v) {
+ public boolean fillInLogContainerData(LauncherLogProto.LauncherEvent event, @Nullable View v) {
// Fill in grid(x,y), pageIndex of the child and container type of the parent
LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(v);
if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
return false;
}
- ItemInfo itemInfo = (ItemInfo) v.getTag();
- provider.fillInLogContainerData(v, itemInfo, event.srcTarget[0], event.srcTarget[1]);
+ final ItemInfo itemInfo = (ItemInfo) v.getTag();
+ final Target target = event.srcTarget[0];
+ final Target targetParent = event.srcTarget[1];
+ onFillInLogContainerData(itemInfo, target, targetParent);
+ provider.fillInLogContainerData(v, itemInfo, target, targetParent);
return true;
}
+ protected void onFillInLogContainerData(
+ @NonNull ItemInfo itemInfo, @NonNull Target target, @NonNull Target targetParent) { }
+
private boolean mSessionStarted;
private long mElapsedContainerMillis;
private long mElapsedSessionMillis;
@@ -115,6 +125,7 @@
protected InstantAppResolver mInstantAppResolver;
private boolean mAppOrTaskLaunch;
private UserEventDelegate mDelegate;
+ private boolean mPreviousHomeGesture;
// APP_ICON SHORTCUT WIDGET
// --------------------------------------------------------------
@@ -138,7 +149,11 @@
mAppOrTaskLaunch = true;
}
- public void logActionTip(int actionType, int viewType) { }
+ /**
+ * Dummy method.
+ */
+ public void logActionTip(int actionType, int viewType) {
+ }
@Deprecated
public void logTaskLaunchOrDismiss(int action, int direction, int taskIndex,
@@ -183,7 +198,15 @@
public void logActionCommand(int command, int srcContainerType, int dstContainerType) {
logActionCommand(command, newContainerTarget(srcContainerType),
- dstContainerType >=0 ? newContainerTarget(dstContainerType) : null);
+ dstContainerType >= 0 ? newContainerTarget(dstContainerType) : null);
+ }
+
+ public void logActionCommand(int command, int srcContainerType, int dstContainerType,
+ int pageIndex) {
+ Target srcTarget = newContainerTarget(srcContainerType);
+ srcTarget.pageIndex = pageIndex;
+ logActionCommand(command, srcTarget,
+ dstContainerType >= 0 ? newContainerTarget(dstContainerType) : null);
}
public void logActionCommand(int command, Target srcTarget, Target dstTarget) {
@@ -232,7 +255,7 @@
}
public void logActionOnControl(int action, int controlType, int parentContainer,
- int grandParentContainer){
+ int grandParentContainer) {
LauncherEvent event = newLauncherEvent(newTouchAction(action),
newControlTarget(controlType),
newContainerTarget(parentContainer),
@@ -241,11 +264,11 @@
}
public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer,
- int parentContainerType) {
+ int parentContainerType) {
final LauncherEvent event = (controlInContainer == null && parentContainerType < 0)
? newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL))
: newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL),
- newTarget(Target.Type.CONTAINER));
+ newTarget(Target.Type.CONTAINER));
event.srcTarget[0].controlType = controlType;
if (controlInContainer != null) {
fillInLogContainerData(event, controlInContainer);
@@ -292,9 +315,9 @@
* (1) WORKSPACE: if the launcher is the foreground activity
* (2) APP: if another app was the foreground activity
*/
- public void logStateChangeAction(int action, int dir, int downX, int downY, int srcChildTargetType,
- int srcParentContainerType, int dstContainerType,
- int pageIndex) {
+ public void logStateChangeAction(int action, int dir, int downX, int downY,
+ int srcChildTargetType, int srcParentContainerType, int dstContainerType,
+ int pageIndex) {
LauncherEvent event;
if (srcChildTargetType == LauncherLogProto.ItemType.TASK) {
event = newLauncherEvent(newTouchAction(action),
@@ -317,9 +340,25 @@
}
public void logActionOnItem(int action, int dir, int itemType) {
+ logActionOnItem(action, dir, itemType, null, null);
+ }
+
+ /**
+ * Creates new {@link LauncherEvent} of ITEM target type with input arguments and dispatches it.
+ *
+ * @param touchAction ENUM value of {@link LauncherLogProto.Action.Touch} Action
+ * @param dir ENUM value of {@link LauncherLogProto.Action.Direction} Action
+ * @param itemType ENUM value of {@link LauncherLogProto.ItemType}
+ * @param gridX Nullable X coordinate of item's position on the workspace grid
+ * @param gridY Nullable Y coordinate of item's position on the workspace grid
+ */
+ public void logActionOnItem(int touchAction, int dir, int itemType,
+ @Nullable Integer gridX, @Nullable Integer gridY) {
Target itemTarget = newTarget(Target.Type.ITEM);
itemTarget.itemType = itemType;
- LauncherEvent event = newLauncherEvent(newTouchAction(action), itemTarget);
+ ofNullable(gridX).ifPresent(value -> itemTarget.gridX = value);
+ ofNullable(gridY).ifPresent(value -> itemTarget.gridY = value);
+ LauncherEvent event = newLauncherEvent(newTouchAction(touchAction), itemTarget);
event.action.dir = dir;
dispatchUserEvent(event, null);
}
@@ -342,7 +381,7 @@
LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
newTarget(Target.Type.CONTAINER));
- event.destTarget = new Target[] {
+ event.destTarget = new Target[]{
newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
newDropTarget(dropTargetAsView)
};
@@ -364,14 +403,10 @@
int actionTouch = isButton ? Action.Touch.TAP : Action.Touch.SWIPE;
Action action = newCommandAction(actionTouch);
action.command = Action.Command.BACK;
- action.dir = isButton
- ? Action.Direction.NONE
- : gestureSwipeLeft
- ? Action.Direction.LEFT
- : Action.Direction.RIGHT;
- Target target = newControlTarget(isButton
- ? LauncherLogProto.ControlType.BACK_BUTTON
- : LauncherLogProto.ControlType.BACK_GESTURE);
+ action.dir = isButton ? Action.Direction.NONE :
+ gestureSwipeLeft ? Action.Direction.LEFT : Action.Direction.RIGHT;
+ Target target = newControlTarget(isButton ? LauncherLogProto.ControlType.BACK_BUTTON :
+ LauncherLogProto.ControlType.BACK_GESTURE);
target.spanX = downX;
target.spanY = downY;
target.cardinality = completed ? 1 : 0;
@@ -382,6 +417,7 @@
/**
* Currently logs following containers: workspace, allapps, widget tray.
+ *
* @param reason
*/
public final void resetElapsedContainerMillis(String reason) {
@@ -399,18 +435,35 @@
mElapsedContainerMillis = SystemClock.uptimeMillis();
}
+ public final void setPreviousHomeGesture(boolean homeGesture) {
+ mPreviousHomeGesture = homeGesture;
+ }
+
+ public final boolean isPreviousHomeGesture() {
+ return mPreviousHomeGesture;
+ }
+
public final void resetActionDurationMillis() {
mActionDurationMillis = SystemClock.uptimeMillis();
}
public void dispatchUserEvent(LauncherEvent ev, Intent intent) {
+ if (mPreviousHomeGesture) {
+ mPreviousHomeGesture = false;
+ }
mAppOrTaskLaunch = false;
ev.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis;
ev.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis;
-
if (!IS_VERBOSE) {
return;
}
+ Log.d(TAG, generateLog(ev));
+ }
+
+ /**
+ * Returns a human-readable log for given user event.
+ */
+ public static String generateLog(LauncherEvent ev) {
String log = "\n-----------------------------------------------------"
+ "\naction:" + LoggerUtils.getActionStr(ev.action);
if (ev.srcTarget != null && ev.srcTarget.length > 0) {
@@ -425,7 +478,7 @@
ev.elapsedSessionMillis,
ev.actionDurationMillis);
log += "\n\n";
- Log.d(TAG, log);
+ return log;
}
private static String getTargetsStr(Target[] targets) {
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index ed0d470..b8c583c 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -16,11 +16,12 @@
package com.android.launcher3.model;
import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageInstaller.SessionInfo;
import android.os.UserHandle;
import android.util.LongSparseArray;
import android.util.Pair;
-import com.android.launcher3.AllAppsList;
import com.android.launcher3.AppInfo;
import com.android.launcher3.FolderInfo;
import com.android.launcher3.InvariantDeviceProfile;
@@ -28,12 +29,14 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.PackageManagerHelper;
import java.util.ArrayList;
import java.util.List;
@@ -73,6 +76,11 @@
if (shortcutExists(dataModel, item.getIntent(), item.user)) {
continue;
}
+
+ // b/139663018 Short-circuit this logic if the icon is a system app
+ if (PackageManagerHelper.isSystemApp(app.getContext(), item.getIntent())) {
+ continue;
+ }
}
if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
@@ -85,6 +93,10 @@
}
}
+ PackageInstallerCompat packageInstaller =
+ PackageInstallerCompat.getInstance(app.getContext());
+ LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(app.getContext());
+
for (ItemInfo item : filteredItems) {
// Find appropriate space for the item.
int[] coords = findSpaceForItem(app, dataModel, workspaceScreens,
@@ -101,6 +113,50 @@
throw new RuntimeException("Unexpected info type");
}
+ if (item instanceof WorkspaceItemInfo && ((WorkspaceItemInfo) item).isPromise()) {
+ WorkspaceItemInfo workspaceInfo = (WorkspaceItemInfo) item;
+ String packageName = item.getTargetComponent() != null
+ ? item.getTargetComponent().getPackageName() : null;
+ if (packageName == null) {
+ continue;
+ }
+ SessionInfo sessionInfo = packageInstaller.getActiveSessionInfo(item.user,
+ packageName);
+ List<LauncherActivityInfo> activities = launcherApps
+ .getActivityList(packageName, item.user);
+ boolean hasActivity = activities != null && !activities.isEmpty();
+
+ if (sessionInfo == null) {
+ if (!hasActivity) {
+ // Session was cancelled, do not add.
+ continue;
+ }
+ } else {
+ workspaceInfo.setInstallProgress((int) sessionInfo.getProgress());
+ }
+
+ if (hasActivity) {
+ // App was installed while launcher was in the background,
+ // or app was already installed for another user.
+ itemInfo = new AppInfo(app.getContext(), activities.get(0), item.user)
+ .makeWorkspaceItem();
+
+ if (shortcutExists(dataModel, itemInfo.getIntent(), itemInfo.user)) {
+ // We need this additional check here since we treat all auto added
+ // workspace items as promise icons. At this point we now have the
+ // correct intent to compare against existing workspace icons.
+ // Icon already exists on the workspace and should not be auto-added.
+ continue;
+ }
+
+ WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo;
+ wii.title = "";
+ wii.applyFrom(app.getIconCache().getDefaultIcon(item.user));
+ app.getIconCache().getTitleAndIcon(wii,
+ ((WorkspaceItemInfo) itemInfo).usingLowResIcon());
+ }
+ }
+
// Add the shortcut to the db
getModelWriter().addItemToDatabase(itemInfo,
LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId,
@@ -162,7 +218,7 @@
intentWithoutPkg = intent.toUri(0);
}
- boolean isLauncherAppTarget = Utilities.isLauncherAppTarget(intent);
+ boolean isLauncherAppTarget = PackageManagerHelper.isLauncherAppTarget(intent);
synchronized (dataModel) {
for (ItemInfo item : dataModel.itemsIdMap) {
if (item instanceof WorkspaceItemInfo) {
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
similarity index 69%
rename from src/com/android/launcher3/AllAppsList.java
rename to src/com/android/launcher3/model/AllAppsList.java
index 733f295..3873a17 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -14,25 +14,37 @@
* limitations under the License.
*/
-package com.android.launcher3;
+package com.android.launcher3.model;
+
+import static com.android.launcher3.AppInfo.COMPONENT_KEY_COMPARATOR;
+import static com.android.launcher3.AppInfo.EMPTY_ARRAY;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
+import android.os.LocaleList;
import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
+import com.android.launcher3.AppFilter;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.PromiseAppInfo;
+import com.android.launcher3.compat.AlphabeticIndexCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.SafeCloseable;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.function.Consumer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -42,29 +54,40 @@
* Stores the list of all applications for the all apps view.
*/
public class AllAppsList {
+
private static final String TAG = "AllAppsList";
+ private static final Consumer<AppInfo> NO_OP_CONSUMER = a -> { };
+
public static final int DEFAULT_APPLICATIONS_NUMBER = 42;
/** The list off all apps. */
public final ArrayList<AppInfo> data = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER);
- /** The list of apps that have been added since the last notify() call. */
- public ArrayList<AppInfo> added = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER);
- /** The list of apps that have been removed since the last notify() call. */
- public ArrayList<AppInfo> removed = new ArrayList<>();
- /** The list of apps that have been modified since the last notify() call. */
- public ArrayList<AppInfo> modified = new ArrayList<>();
private IconCache mIconCache;
-
private AppFilter mAppFilter;
+ private boolean mDataChanged = false;
+ private Consumer<AppInfo> mRemoveListener = NO_OP_CONSUMER;
+
+ private AlphabeticIndexCompat mIndex;
+
/**
* Boring constructor.
*/
public AllAppsList(IconCache iconCache, AppFilter appFilter) {
mIconCache = iconCache;
mAppFilter = appFilter;
+ mIndex = new AlphabeticIndexCompat(LocaleList.getDefault());
+ }
+
+ /**
+ * Returns true if there have been any changes since last call.
+ */
+ public boolean getAndResetChangeFlag() {
+ boolean result = mDataChanged;
+ mDataChanged = false;
+ return result;
}
/**
@@ -81,44 +104,60 @@
return;
}
mIconCache.getTitleAndIcon(info, activityInfo, true /* useLowResIcon */);
+ info.sectionName = mIndex.computeSectionName(info.title);
data.add(info);
- added.add(info);
+ mDataChanged = true;
}
public void addPromiseApp(Context context,
PackageInstallerCompat.PackageInstallInfo installInfo) {
ApplicationInfo applicationInfo = LauncherAppsCompat.getInstance(context)
- .getApplicationInfo(installInfo.packageName, 0, Process.myUserHandle());
+ .getApplicationInfo(installInfo.packageName, 0, installInfo.user);
// only if not yet installed
if (applicationInfo == null) {
PromiseAppInfo info = new PromiseAppInfo(installInfo);
mIconCache.getTitleAndIcon(info, info.usingLowResIcon());
+ info.sectionName = mIndex.computeSectionName(info.title);
+
data.add(info);
- added.add(info);
+ mDataChanged = true;
}
}
- public void removePromiseApp(AppInfo appInfo) {
- // the <em>removed</em> list is handled by the caller
- // so not adding it here
- data.remove(appInfo);
+ public PromiseAppInfo updatePromiseInstallInfo(PackageInstallInfo installInfo) {
+ UserHandle user = Process.myUserHandle();
+ for (int i=0; i < data.size(); i++) {
+ final AppInfo appInfo = data.get(i);
+ final ComponentName tgtComp = appInfo.getTargetComponent();
+ if (tgtComp != null && tgtComp.getPackageName().equals(installInfo.packageName)
+ && appInfo.user.equals(user)
+ && appInfo instanceof PromiseAppInfo) {
+ final PromiseAppInfo promiseAppInfo = (PromiseAppInfo) appInfo;
+ if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLING) {
+ promiseAppInfo.level = installInfo.progress;
+ return promiseAppInfo;
+ } else if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
+ removeApp(i);
+ }
+ }
+ }
+ return null;
+ }
+
+ private void removeApp(int index) {
+ AppInfo removed = data.remove(index);
+ if (removed != null) {
+ mDataChanged = true;
+ mRemoveListener.accept(removed);
+ }
}
public void clear() {
data.clear();
- // TODO: do we clear these too?
- added.clear();
- removed.clear();
- modified.clear();
- }
-
- public int size() {
- return data.size();
- }
-
- public AppInfo get(int index) {
- return data.get(index);
+ mDataChanged = false;
+ // Reset the index as locales might have changed
+ mIndex = new AlphabeticIndexCompat(LocaleList.getDefault());
}
/**
@@ -142,8 +181,7 @@
for (int i = data.size() - 1; i >= 0; i--) {
AppInfo info = data.get(i);
if (info.user.equals(user) && packageName.equals(info.componentName.getPackageName())) {
- removed.add(info);
- data.remove(i);
+ removeApp(i);
}
}
}
@@ -157,17 +195,17 @@
AppInfo info = data.get(i);
if (matcher.matches(info, info.componentName)) {
info.runtimeStatusFlags = op.apply(info.runtimeStatusFlags);
- modified.add(info);
+ mDataChanged = true;
}
}
}
- public void updateIconsAndLabels(HashSet<String> packages, UserHandle user,
- ArrayList<AppInfo> outUpdates) {
+ public void updateIconsAndLabels(HashSet<String> packages, UserHandle user) {
for (AppInfo info : data) {
if (info.user.equals(user) && packages.contains(info.componentName.getPackageName())) {
mIconCache.updateTitleAndIcon(info);
- outUpdates.add(info);
+ info.sectionName = mIndex.computeSectionName(info.title);
+ mDataChanged = true;
}
}
}
@@ -188,8 +226,7 @@
&& packageName.equals(applicationInfo.componentName.getPackageName())) {
if (!findActivity(matches, applicationInfo.componentName)) {
Log.w(TAG, "Changing shortcut target due to app component name change.");
- removed.add(applicationInfo);
- data.remove(i);
+ removeApp(i);
}
}
}
@@ -202,7 +239,9 @@
add(new AppInfo(context, info, user), info);
} else {
mIconCache.getTitleAndIcon(applicationInfo, info, true /* useLowResIcon */);
- modified.add(applicationInfo);
+ applicationInfo.sectionName = mIndex.computeSectionName(applicationInfo.title);
+
+ mDataChanged = true;
}
}
} else {
@@ -211,15 +250,13 @@
final AppInfo applicationInfo = data.get(i);
if (user.equals(applicationInfo.user)
&& packageName.equals(applicationInfo.componentName.getPackageName())) {
- removed.add(applicationInfo);
mIconCache.remove(applicationInfo.componentName, user);
- data.remove(i);
+ removeApp(i);
}
}
}
}
-
/**
* Returns whether <em>apps</em> contains <em>component</em>.
*/
@@ -247,4 +284,16 @@
}
return null;
}
+
+ public AppInfo[] copyData() {
+ AppInfo[] result = data.toArray(EMPTY_ARRAY);
+ Arrays.sort(result, COMPONENT_KEY_COMPARATOR);
+ return result;
+ }
+
+ public SafeCloseable trackRemoves(Consumer<AppInfo> removeListener) {
+ mRemoveListener = removeListener;
+
+ return () -> mRemoveListener = NO_OP_CONSUMER;
+ }
}
diff --git a/src/com/android/launcher3/model/AppLaunchTracker.java b/src/com/android/launcher3/model/AppLaunchTracker.java
index 1613d47..13ab033 100644
--- a/src/com/android/launcher3/model/AppLaunchTracker.java
+++ b/src/com/android/launcher3/model/AppLaunchTracker.java
@@ -15,18 +15,18 @@
*/
package com.android.launcher3.model;
-import static com.android.launcher3.util.ResourceBasedOverride.Overrides.getObject;
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
import android.content.ComponentName;
import android.os.UserHandle;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.R;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.ResourceBasedOverride;
-import androidx.annotation.Nullable;
-
/**
* Callback for receiving various app launch events
*/
@@ -43,8 +43,7 @@
public static final MainThreadInitializedObject<AppLaunchTracker> INSTANCE =
- new MainThreadInitializedObject<>(c ->
- getObject(AppLaunchTracker.class, c, R.string.app_launch_tracker_class));
+ forOverride(AppLaunchTracker.class, R.string.app_launch_tracker_class);
public void onStartShortcut(String packageName, String shortcutId, UserHandle user,
@Nullable String container) { }
@@ -52,5 +51,8 @@
public void onStartApp(ComponentName componentName, UserHandle user,
@Nullable String container) { }
+ public void onDismissApp(ComponentName componentName, UserHandle user,
+ @Nullable String container){}
+
public void onReturnedToHome() { }
}
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 97cf267..0a4f005 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -16,21 +16,21 @@
package com.android.launcher3.model;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
import android.os.Looper;
import android.util.Log;
-import com.android.launcher3.AllAppsList;
import com.android.launcher3.AppInfo;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherModel.Callbacks;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.PagedView;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.LooperIdleLock;
@@ -65,7 +65,7 @@
public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel,
AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
- mUiExecutor = new MainThreadExecutor();
+ mUiExecutor = MAIN_EXECUTOR;
mApp = app;
mBgDataModel = dataModel;
mBgAllAppsList = allAppsList;
@@ -279,9 +279,8 @@
public void bindAllApps() {
// shallow copy
- @SuppressWarnings("unchecked")
- ArrayList<AppInfo> list = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
- executeCallbacksTask(c -> c.bindAllApplications(list), mUiExecutor);
+ AppInfo[] apps = mBgAllAppsList.copyData();
+ executeCallbacksTask(c -> c.bindAllApplications(apps), mUiExecutor);
}
public abstract void bindWidgets();
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index eea3d8c..e12633b 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -17,12 +17,12 @@
import android.util.Log;
-import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -30,6 +30,7 @@
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -95,12 +96,7 @@
public void bindUpdatedWorkspaceItems(final ArrayList<WorkspaceItemInfo> updatedShortcuts) {
if (!updatedShortcuts.isEmpty()) {
- scheduleCallbackTask(new CallbackTask() {
- @Override
- public void execute(Callbacks callbacks) {
- callbacks.bindWorkspaceItemsChanged(updatedShortcuts);
- }
- });
+ scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(updatedShortcuts));
}
}
@@ -113,23 +109,20 @@
public void bindUpdatedWidgets(BgDataModel dataModel) {
final ArrayList<WidgetListRowEntry> widgets =
dataModel.widgetsModel.getWidgetsList(mApp.getContext());
- scheduleCallbackTask(new CallbackTask() {
- @Override
- public void execute(Callbacks callbacks) {
- callbacks.bindAllWidgets(widgets);
- }
- });
+ scheduleCallbackTask(c -> c.bindAllWidgets(widgets));
}
public void deleteAndBindComponentsRemoved(final ItemInfoMatcher matcher) {
getModelWriter().deleteItemsFromDatabase(matcher);
// Call the components-removed callback
- scheduleCallbackTask(new CallbackTask() {
- @Override
- public void execute(Callbacks callbacks) {
- callbacks.bindWorkspaceComponentsRemoved(matcher);
- }
- });
+ scheduleCallbackTask(c -> c.bindWorkspaceComponentsRemoved(matcher));
+ }
+
+ public void bindApplicationsIfNeeded() {
+ if (mAllAppsList.getAndResetChangeFlag()) {
+ AppInfo[] apps = mAllAppsList.copyData();
+ scheduleCallbackTask(c -> c.bindAllApplications(apps));
+ }
}
}
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 8f0cd08..0e20270 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -22,11 +22,13 @@
import android.util.Log;
import android.util.MutableInt;
+import com.android.launcher3.AppInfo;
import com.android.launcher3.FolderInfo;
import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.PromiseAppInfo;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
@@ -40,6 +42,10 @@
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.widget.WidgetListRowEntry;
+
import com.google.protobuf.nano.MessageNano;
import java.io.FileDescriptor;
@@ -49,6 +55,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -391,4 +398,30 @@
}
}
}
+
+ public interface Callbacks {
+ void rebindModel();
+
+ int getCurrentWorkspaceScreen();
+ void clearPendingBinds();
+ void startBinding();
+ void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
+ void bindScreens(IntArray orderedScreenIds);
+ void finishFirstPageBind(ViewOnDrawExecutor executor);
+ void finishBindingItems(int pageBoundFirst);
+ void preAddApps();
+ void bindAppsAdded(IntArray newScreens,
+ ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated);
+ void bindPromiseAppProgressUpdated(PromiseAppInfo app);
+ void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated);
+ void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
+ void bindRestoreItemsChange(HashSet<ItemInfo> updates);
+ void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
+ void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets);
+ void onPageBoundSynchronously(int page);
+ void executeOnNextDraw(ViewOnDrawExecutor executor);
+ void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
+
+ void bindAllApplications(AppInfo[] apps);
+ }
}
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index 7852444..c1c8be3 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -18,14 +18,10 @@
import android.content.ComponentName;
import android.os.UserHandle;
-import com.android.launcher3.AllAppsList;
-import com.android.launcher3.AppInfo;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherModel.Callbacks;
import com.android.launcher3.LauncherSettings;
import java.util.ArrayList;
@@ -53,9 +49,9 @@
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
IconCache iconCache = app.getIconCache();
- final ArrayList<AppInfo> updatedApps = new ArrayList<>();
ArrayList<WorkspaceItemInfo> updatedShortcuts = new ArrayList<>();
+
synchronized (dataModel) {
for (ItemInfo info : dataModel.itemsIdMap) {
if (info instanceof WorkspaceItemInfo && mUser.equals(info.user)) {
@@ -69,18 +65,10 @@
}
}
}
- apps.updateIconsAndLabels(mPackages, mUser, updatedApps);
+ apps.updateIconsAndLabels(mPackages, mUser);
}
bindUpdatedWorkspaceItems(updatedShortcuts);
-
- if (!updatedApps.isEmpty()) {
- scheduleCallbackTask(new CallbackTask() {
- @Override
- public void execute(Callbacks callbacks) {
- callbacks.bindAppsAddedOrUpdated(updatedApps);
- }
- });
- }
+ bindApplicationsIfNeeded();
}
public boolean isValidShortcut(WorkspaceItemInfo si) {
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java
index b8dbd08..3f7ba21 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcast.java
+++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java
@@ -35,6 +35,7 @@
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
import java.util.HashMap;
@@ -43,6 +44,8 @@
import java.util.Map;
import java.util.Set;
+import static android.os.Process.myUserHandle;
+
/**
* Helper class to send broadcasts to package installers that have:
* - Items on the first screen
@@ -69,7 +72,7 @@
private final MultiHashMap<String, String> mPackagesForInstaller;
- public FirstScreenBroadcast(HashMap<String, SessionInfo> sessionInfoForPackage) {
+ public FirstScreenBroadcast(HashMap<PackageUserKey, SessionInfo> sessionInfoForPackage) {
mPackagesForInstaller = getPackagesForInstaller(sessionInfoForPackage);
}
@@ -78,11 +81,13 @@
* of packages with active sessions for that installer.
*/
private MultiHashMap<String, String> getPackagesForInstaller(
- HashMap<String, SessionInfo> sessionInfoForPackage) {
+ HashMap<PackageUserKey, SessionInfo> sessionInfoForPackage) {
MultiHashMap<String, String> packagesForInstaller = new MultiHashMap<>();
- for (Map.Entry<String, SessionInfo> entry : sessionInfoForPackage.entrySet()) {
- packagesForInstaller.addToList(entry.getValue().getInstallerPackageName(),
- entry.getKey());
+ for (Map.Entry<PackageUserKey, SessionInfo> entry : sessionInfoForPackage.entrySet()) {
+ if (myUserHandle().equals(entry.getKey().mUser)) {
+ packagesForInstaller.addToList(entry.getValue().getInstallerPackageName(),
+ entry.getKey().mPackageName);
+ }
}
return packagesForInstaller;
}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index faecc06..783e908 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -34,10 +34,12 @@
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.function.Consumer;
import androidx.annotation.VisibleForTesting;
@@ -970,8 +972,9 @@
.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
validPackages.add(info.packageName);
}
- validPackages.addAll(PackageInstallerCompat.getInstance(context)
- .updateAndGetActiveSessionCache().keySet());
+ PackageInstallerCompat.getInstance(context)
+ .updateAndGetActiveSessionCache().keySet()
+ .forEach(packageUserKey -> validPackages.add(packageUserKey.mPackageName));
return validPackages;
}
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 1a03b77..1c39d1f 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -227,7 +227,7 @@
if (!TextUtils.isEmpty(title)) {
info.title = Utilities.trim(title);
}
- } else if (hasRestoreFlag(WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON)) {
+ } else if (hasRestoreFlag(WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON)) {
if (TextUtils.isEmpty(info.title)) {
info.title = getTitle();
}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 0138572..50e1d56 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -20,6 +20,8 @@
import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
import static com.android.launcher3.model.LoaderResults.filterCurrentWorkspaceItems;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.PackageManagerHelper.isSystemApp;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
@@ -31,7 +33,6 @@
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.ShortcutInfo;
-import android.os.Handler;
import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -39,7 +40,6 @@
import android.util.LongSparseArray;
import android.util.MutableInt;
-import com.android.launcher3.AllAppsList;
import com.android.launcher3.AppInfo;
import com.android.launcher3.FolderInfo;
import com.android.launcher3.InstallShortcutReceiver;
@@ -49,29 +49,32 @@
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderIconPreviewVerifier;
+import com.android.launcher3.folder.FolderGridOrganizer;
import com.android.launcher3.icons.ComponentWithLabel;
import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.LauncherActivtiyCachingLogic;
+import com.android.launcher3.icons.LauncherActivityCachingLogic;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.provider.ImportDataTask;
+import com.android.launcher3.qsb.QsbContainerView;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.IOUtils;
import com.android.launcher3.util.LooperIdleLock;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.TraceHelper;
import java.util.ArrayList;
@@ -196,7 +199,7 @@
IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
setIgnorePackages(updateHandler);
updateHandler.updateIcons(allActivityList,
- new LauncherActivtiyCachingLogic(mApp.getIconCache()),
+ LauncherActivityCachingLogic.newInstance(mApp.getContext()),
mApp.getModel()::onPackageIconsUpdated);
// Take a break
@@ -226,9 +229,10 @@
mResults.bindWidgets();
verifyNotStopped();
- TraceHelper.partitionSection(TAG, "step 4.3: Update icon cache");
- updateHandler.updateIcons(allWidgetsList, new ComponentCachingLogic(mApp.getContext()),
- mApp.getModel()::onWidgetLabelsUpdated);
+
+ TraceHelper.partitionSection(TAG, "step 4.3: save widgets in icon cache");
+ updateHandler.updateIcons(allWidgetsList, new ComponentCachingLogic(
+ mApp.getContext(), true), mApp.getModel()::onWidgetLabelsUpdated);
verifyNotStopped();
TraceHelper.partitionSection(TAG, "step 5: Finish icon cache update");
@@ -281,8 +285,9 @@
synchronized (mBgDataModel) {
mBgDataModel.clear();
- final HashMap<String, SessionInfo> installingPkgs =
+ final HashMap<PackageUserKey, SessionInfo> installingPkgs =
mPackageInstaller.updateAndGetActiveSessionCache();
+ final PackageUserKey tempPackageKey = new PackageUserKey(null, null);
mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
Map<ShortcutKey, ShortcutInfo> shortcutKeyToPinnedShortcuts = new HashMap<>();
@@ -317,9 +322,9 @@
// We can only query for shortcuts when the user is unlocked.
if (userUnlocked) {
- List<ShortcutInfo> pinnedShortcuts =
+ DeepShortcutManager.QueryResult pinnedShortcuts =
mShortcutManager.queryForPinnedShortcuts(null, user);
- if (mShortcutManager.wasLastCallSuccess()) {
+ if (pinnedShortcuts.wasSuccess()) {
for (ShortcutInfo shortcut : pinnedShortcuts) {
shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
shortcut);
@@ -384,7 +389,9 @@
boolean validTarget = TextUtils.isEmpty(targetPkg) ||
mLauncherApps.isPackageEnabledForProfile(targetPkg, c.user);
- if (cn != null && validTarget) {
+ // If it's a deep shortcut, we'll use pinned shortcuts to restore it
+ if (cn != null && validTarget && c.itemType
+ != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
// If the apk is present and the shortcut points to a specific
// component.
@@ -419,9 +426,10 @@
// installed later.
FileLog.d(TAG, "package not yet restored: " + targetPkg);
+ tempPackageKey.update(targetPkg, c.user);
if (c.hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORE_STARTED)) {
// Restore has started once.
- } else if (installingPkgs.containsKey(targetPkg)) {
+ } else if (installingPkgs.containsKey(tempPackageKey)) {
// App restore has started. Update the flag
c.restoreFlag |= WorkspaceItemInfo.FLAG_RESTORE_STARTED;
c.updater().put(LauncherSettings.Favorites.RESTORED,
@@ -531,12 +539,13 @@
info.spanX = 1;
info.spanY = 1;
info.runtimeStatusFlags |= disabledState;
- if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
+ if (isSafeMode && !isSystemApp(context, intent)) {
info.runtimeStatusFlags |= FLAG_DISABLED_SAFEMODE;
}
if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) {
- SessionInfo si = installingPkgs.get(targetPkg);
+ tempPackageKey.update(targetPkg, c.user);
+ SessionInfo si = installingPkgs.get(tempPackageKey);
if (si == null) {
info.status &= ~WorkspaceItemInfo.FLAG_INSTALL_SESSION_ACTIVE;
} else {
@@ -579,10 +588,19 @@
int appWidgetId = c.getInt(appWidgetIdIndex);
String savedProvider = c.getString(appWidgetProviderIndex);
+ final ComponentName component;
- final ComponentName component =
- ComponentName.unflattenFromString(savedProvider);
-
+ boolean isSearchWidget = (c.getInt(optionsIndex)
+ & LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET) != 0;
+ if (isSearchWidget) {
+ component = QsbContainerView.getSearchComponentName(context);
+ if (component == null) {
+ c.markDeleted("Discarding SearchWidget without packagename ");
+ continue;
+ }
+ } else {
+ component = ComponentName.unflattenFromString(savedProvider);
+ }
final boolean isIdValid = !c.hasRestoreFlag(
LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
final boolean wasProviderReady = !c.hasRestoreFlag(
@@ -592,9 +610,7 @@
widgetProvidersMap = mAppWidgetManager.getAllProvidersMap();
}
final AppWidgetProviderInfo provider = widgetProvidersMap.get(
- new ComponentKey(
- ComponentName.unflattenFromString(savedProvider),
- c.user));
+ new ComponentKey(component, c.user));
final boolean isProviderReady = isValidProvider(provider);
if (!isSafeMode && !customWidget &&
@@ -630,8 +646,10 @@
appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
component);
appWidgetInfo.restoreStatus = c.restoreFlag;
+
+ tempPackageKey.update(component.getPackageName(), c.user);
SessionInfo si =
- installingPkgs.get(component.getPackageName());
+ installingPkgs.get(tempPackageKey);
Integer installProgress = si == null
? null
: (int) (si.getProgress() * 100);
@@ -658,6 +676,7 @@
c.applyCommonProperties(appWidgetInfo);
appWidgetInfo.spanX = c.getInt(spanXIndex);
appWidgetInfo.spanY = c.getInt(spanYIndex);
+ appWidgetInfo.options = c.getInt(optionsIndex);
appWidgetInfo.user = c.user;
if (appWidgetInfo.spanX <= 0 || appWidgetInfo.spanY <= 0) {
@@ -703,7 +722,7 @@
}
}
} finally {
- Utilities.closeSilently(c);
+ IOUtils.closeSilently(c);
}
// Break early if we've stopped loading
@@ -743,8 +762,8 @@
}
// Sort the folder items, update ranks, and make sure all preview items are high res.
- FolderIconPreviewVerifier verifier =
- new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile());
+ FolderGridOrganizer verifier =
+ new FolderGridOrganizer(mApp.getInvariantDeviceProfile());
for (FolderInfo folder : mBgDataModel.folders) {
Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
verifier.setFolderInfo(folder);
@@ -771,7 +790,7 @@
new SdCardAvailableReceiver(mApp, pendingPackages),
new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
null,
- new Handler(LauncherModel.getWorkerLooper()));
+ MODEL_EXECUTOR.getHandler());
}
}
}
@@ -829,7 +848,7 @@
}
}
- mBgAllAppsList.added = new ArrayList<>();
+ mBgAllAppsList.getAndResetChangeFlag();
return allActivityList;
}
diff --git a/src/com/android/launcher3/model/ModelPreload.java b/src/com/android/launcher3/model/ModelPreload.java
index b353810..2bd6cd4 100644
--- a/src/com/android/launcher3/model/ModelPreload.java
+++ b/src/com/android/launcher3/model/ModelPreload.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.util.Log;
-import com.android.launcher3.AllAppsList;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherModel.ModelUpdateTask;
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 4ce2f4b..bdf3a69 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -16,6 +16,8 @@
package com.android.launcher3.model;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -31,23 +33,25 @@
import com.android.launcher3.LauncherAppWidgetHost;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherModel.Callbacks;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.Settings;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.LooperExecutor;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
/**
* Class for handling model updates.
@@ -61,7 +65,6 @@
private final BgDataModel mBgDataModel;
private final Handler mUiHandler;
- private final Executor mWorkerExecutor;
private final boolean mHasVerticalHotseat;
private final boolean mVerifyChanges;
@@ -74,7 +77,6 @@
mContext = context;
mModel = model;
mBgDataModel = dataModel;
- mWorkerExecutor = new LooperExecutor(LauncherModel.getWorkerLooper());
mHasVerticalHotseat = hasVerticalHotseat;
mVerifyChanges = verifyChanges;
mUiHandler = new Handler(Looper.getMainLooper());
@@ -101,7 +103,7 @@
*/
public void addOrMoveItemInDatabase(ItemInfo item,
int container, int screenId, int cellX, int cellY) {
- if (item.container == ItemInfo.NO_ID) {
+ if (item.id == ItemInfo.NO_ID) {
// From all apps
addItemToDatabase(item, container, screenId, cellX, cellY);
} else {
@@ -194,7 +196,7 @@
item.spanX = spanX;
item.spanY = spanY;
- mWorkerExecutor.execute(new UpdateItemRunnable(item, () ->
+ ((Executor) MODEL_EXECUTOR).execute(new UpdateItemRunnable(item, () ->
new ContentWriter(mContext)
.put(Favorites.CONTAINER, item.container)
.put(Favorites.CELLX, item.cellX)
@@ -209,7 +211,7 @@
* Update an item to the database in a specified container.
*/
public void updateItemInDatabase(ItemInfo item) {
- mWorkerExecutor.execute(new UpdateItemRunnable(item, () -> {
+ ((Executor) MODEL_EXECUTOR).execute(new UpdateItemRunnable(item, () -> {
ContentWriter writer = new ContentWriter(mContext);
item.onAddToDatabase(writer);
return writer;
@@ -229,7 +231,7 @@
ModelVerifier verifier = new ModelVerifier();
final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
- mWorkerExecutor.execute(() -> {
+ ((Executor) MODEL_EXECUTOR).execute(() -> {
// Write the item on background thread, as some properties might have been updated in
// the background.
final ContentWriter writer = new ContentWriter(mContext);
@@ -263,9 +265,12 @@
/**
* Removes the specified items from the database
*/
- public void deleteItemsFromDatabase(final Iterable<? extends ItemInfo> items) {
+ public void deleteItemsFromDatabase(final Collection<? extends ItemInfo> items) {
ModelVerifier verifier = new ModelVerifier();
-
+ FileLog.d(TAG, "removing items from db " + items.stream().map(
+ (item) -> item.getTargetComponent() == null ? ""
+ : item.getTargetComponent().getPackageName()).collect(
+ Collectors.joining(",")), new Exception());
enqueueDeleteRunnable(() -> {
for (ItemInfo item : items) {
final Uri uri = Favorites.getContentUri(item.id);
@@ -333,14 +338,14 @@
if (mPreparingToUndo) {
mDeleteRunnables.add(r);
} else {
- mWorkerExecutor.execute(r);
+ ((Executor) MODEL_EXECUTOR).execute(r);
}
}
public void commitDelete() {
mPreparingToUndo = false;
for (Runnable runnable : mDeleteRunnables) {
- mWorkerExecutor.execute(runnable);
+ ((Executor) MODEL_EXECUTOR).execute(runnable);
}
mDeleteRunnables.clear();
}
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index 5f6d128..802cbc7 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -18,22 +18,19 @@
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.os.Process;
-import com.android.launcher3.AllAppsList;
import com.android.launcher3.AppInfo;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.PromiseAppInfo;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.util.InstantAppResolver;
-import java.util.ArrayList;
import java.util.HashSet;
/**
@@ -56,7 +53,7 @@
ApplicationInfo ai = app.getContext()
.getPackageManager().getApplicationInfo(mInstallInfo.packageName, 0);
if (InstantAppResolver.newInstance(app.getContext()).isInstantApp(ai)) {
- app.getModel().onPackageAdded(ai.packageName, Process.myUserHandle());
+ app.getModel().onPackageAdded(ai.packageName, mInstallInfo.user);
}
} catch (PackageManager.NameNotFoundException e) {
// Ignore
@@ -66,41 +63,11 @@
}
synchronized (apps) {
- PromiseAppInfo updated = null;
- final ArrayList<AppInfo> removed = new ArrayList<>();
- for (int i=0; i < apps.size(); i++) {
- final AppInfo appInfo = apps.get(i);
- final ComponentName tgtComp = appInfo.getTargetComponent();
- if (tgtComp != null && tgtComp.getPackageName().equals(mInstallInfo.packageName)) {
- if (appInfo instanceof PromiseAppInfo) {
- final PromiseAppInfo promiseAppInfo = (PromiseAppInfo) appInfo;
- if (mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLING) {
- promiseAppInfo.level = mInstallInfo.progress;
- updated = promiseAppInfo;
- } else if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED) {
- apps.removePromiseApp(appInfo);
- removed.add(appInfo);
- }
- }
- }
- }
+ PromiseAppInfo updated = apps.updatePromiseInstallInfo(mInstallInfo);
if (updated != null) {
- final PromiseAppInfo updatedPromiseApp = updated;
- scheduleCallbackTask(new CallbackTask() {
- @Override
- public void execute(Callbacks callbacks) {
- callbacks.bindPromiseAppProgressUpdated(updatedPromiseApp);
- }
- });
+ scheduleCallbackTask(c -> c.bindPromiseAppProgressUpdated(updated));
}
- if (!removed.isEmpty()) {
- scheduleCallbackTask(new CallbackTask() {
- @Override
- public void execute(Callbacks callbacks) {
- callbacks.bindAppInfosRemoved(removed);
- }
- });
- }
+ bindApplicationsIfNeeded();
}
synchronized (dataModel) {
diff --git a/src/com/android/launcher3/model/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java
index baeaa94..3ef48cd 100644
--- a/src/com/android/launcher3/model/PackageItemInfo.java
+++ b/src/com/android/launcher3/model/PackageItemInfo.java
@@ -17,6 +17,7 @@
package com.android.launcher3.model;
import com.android.launcher3.ItemInfoWithIcon;
+import com.android.launcher3.LauncherSettings;
/**
* Represents a {@link Package} in the widget tray section.
@@ -30,10 +31,21 @@
public PackageItemInfo(String packageName) {
this.packageName = packageName;
+ this.itemType = LauncherSettings.Favorites.ITEM_TYPE_NON_ACTIONABLE;
+ }
+
+ public PackageItemInfo(PackageItemInfo copy) {
+ this.packageName = copy.packageName;
+ this.itemType = LauncherSettings.Favorites.ITEM_TYPE_NON_ACTIONABLE;
}
@Override
protected String dumpProperties() {
return super.dumpProperties() + " packageName=" + packageName;
}
+
+ @Override
+ public PackageItemInfo clone() {
+ return new PackageItemInfo(this);
+ }
}
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index c37ed99..d6ebaaf 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.model;
+import static com.android.launcher3.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -23,31 +25,29 @@
import android.os.UserHandle;
import android.util.Log;
-import com.android.launcher3.AllAppsList;
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.IconCache;
import com.android.launcher3.InstallShortcutReceiver;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetInfo;
-import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherModel.Callbacks;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.SessionCommitReceiver;
import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.SafeCloseable;
import java.util.ArrayList;
import java.util.Arrays;
@@ -55,6 +55,9 @@
import java.util.HashSet;
import java.util.List;
+import static com.android.launcher3.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
+import static com.android.launcher3.WorkspaceItemInfo.FLAG_RESTORED_ICON;
+
/**
* Handles updates due to changes in package manager (app installed/updated/removed)
* or when a user availability changes.
@@ -85,6 +88,10 @@
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.APP_NOT_DISABLED, "PackageUpdatedTask: " + mOp + ", " +
+ Arrays.toString(mPackages));
+ }
final Context context = app.getContext();
final IconCache iconCache = app.getIconCache();
@@ -93,13 +100,15 @@
FlagOp flagOp = FlagOp.NO_OP;
final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageSet, mUser);
+ final HashSet<ComponentName> removedComponents = new HashSet<>();
+
switch (mOp) {
case OP_ADD: {
for (int i = 0; i < N; i++) {
if (DEBUG) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
iconCache.updateIconsForPkg(packages[i], mUser);
if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) {
- appsList.removePackage(packages[i], Process.myUserHandle());
+ appsList.removePackage(packages[i], mUser);
}
appsList.addPackage(context, packages[i], mUser);
@@ -112,17 +121,21 @@
break;
}
case OP_UPDATE:
- for (int i = 0; i < N; i++) {
- if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
- iconCache.updateIconsForPkg(packages[i], mUser);
- appsList.updatePackage(context, packages[i], mUser);
- app.getWidgetCache().removePackage(packages[i], mUser);
+ try (SafeCloseable t =
+ appsList.trackRemoves(a -> removedComponents.add(a.componentName))) {
+ for (int i = 0; i < N; i++) {
+ if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
+ iconCache.updateIconsForPkg(packages[i], mUser);
+ appsList.updatePackage(context, packages[i], mUser);
+ app.getWidgetCache().removePackage(packages[i], mUser);
+ }
}
// Since package was just updated, the target must be available now.
flagOp = FlagOp.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
break;
case OP_REMOVE: {
for (int i = 0; i < N; i++) {
+ FileLog.d(TAG, "Removing app icon" + packages[i]);
iconCache.removeIconsForPkg(packages[i], mUser);
}
// Fall through
@@ -153,23 +166,7 @@
break;
}
- final ArrayList<AppInfo> addedOrModified = new ArrayList<>();
- addedOrModified.addAll(appsList.added);
- appsList.added.clear();
- addedOrModified.addAll(appsList.modified);
- appsList.modified.clear();
- if (!addedOrModified.isEmpty()) {
- scheduleCallbackTask((callbacks) -> callbacks.bindAppsAddedOrUpdated(addedOrModified));
- }
-
- final ArrayList<AppInfo> removedApps = new ArrayList<>(appsList.removed);
- appsList.removed.clear();
- final HashSet<ComponentName> removedComponents = new HashSet<>();
- if (mOp == OP_UPDATE) {
- for (AppInfo ai : removedApps) {
- removedComponents.add(ai.componentName);
- }
- }
+ bindApplicationsIfNeeded();
final IntSparseArrayMap<Boolean> removedShortcuts = new IntSparseArrayMap<>();
@@ -227,8 +224,7 @@
isTargetValid = LauncherAppsCompat.getInstance(context)
.isActivityEnabledForProfile(cn, mUser);
}
- if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON)
- && !isTargetValid) {
+ if (si.hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)) {
if (updateWorkspaceItemIntent(context, si, packageName)) {
infoUpdated = true;
} else if (si.hasPromiseIconUi()) {
@@ -296,12 +292,7 @@
}
if (!widgets.isEmpty()) {
- scheduleCallbackTask(new CallbackTask() {
- @Override
- public void execute(Callbacks callbacks) {
- callbacks.bindWidgetsRestored(widgets);
- }
- });
+ scheduleCallbackTask(c -> c.bindWidgetsRestored(widgets));
}
}
@@ -332,16 +323,6 @@
InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
}
- if (!removedApps.isEmpty()) {
- // Remove corresponding apps from All-Apps
- scheduleCallbackTask(new CallbackTask() {
- @Override
- public void execute(Callbacks callbacks) {
- callbacks.bindAppInfosRemoved(removedApps);
- }
- });
- }
-
if (Utilities.ATLEAST_OREO && mOp == OP_ADD) {
// Load widgets for the new package. Changes due to app updates are handled through
// AppWidgetHost events, this is just to initialize the long-press options.
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 8528228..6c358b1 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -19,7 +19,6 @@
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
-import com.android.launcher3.AllAppsList;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
@@ -65,7 +64,7 @@
for (ItemInfo itemInfo : dataModel.itemsIdMap) {
if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
WorkspaceItemInfo si = (WorkspaceItemInfo) itemInfo;
- if (si.getIntent().getPackage().equals(mPackageName) && si.user.equals(mUser)) {
+ if (mPackageName.equals(si.getIntent().getPackage()) && si.user.equals(mUser)) {
keyToShortcutInfo.addToList(ShortcutKey.fromItemInfo(si), si);
allIds.add(si.getDeepShortcutId());
}
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 2cb256e..4b773d7 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -21,7 +21,6 @@
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
-import com.android.launcher3.AllAppsList;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
@@ -37,7 +36,6 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.List;
/**
* Task to handle changing of lock state of the user
@@ -58,9 +56,9 @@
HashMap<ShortcutKey, ShortcutInfo> pinnedShortcuts = new HashMap<>();
if (isUserUnlocked) {
- List<ShortcutInfo> shortcuts =
+ DeepShortcutManager.QueryResult shortcuts =
deepShortcutManager.queryForPinnedShortcuts(null, mUser);
- if (deepShortcutManager.wasLastCallSuccess()) {
+ if (shortcuts.wasSuccess()) {
for (ShortcutInfo shortcut : shortcuts) {
pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
}
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index 32410a6..021fb30 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -16,6 +16,8 @@
package com.android.launcher3.notification;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
+
import android.app.Notification;
import android.content.Context;
import android.graphics.Color;
@@ -28,13 +30,11 @@
import com.android.launcher3.R;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.popup.PopupContainerWithArrow;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.Themes;
import java.util.List;
-import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL;
-
/**
* Utility class to manage notification UI
*/
@@ -49,7 +49,7 @@
private final TextView mHeaderCount;
private final NotificationMainView mMainView;
private final NotificationFooterLayout mFooter;
- private final SwipeDetector mSwipeDetector;
+ private final SingleAxisSwipeDetector mSwipeDetector;
private final View mIconView;
private final View mHeader;
@@ -74,8 +74,8 @@
mHeader = container.findViewById(R.id.header);
mDivider = container.findViewById(R.id.divider);
- mSwipeDetector = new SwipeDetector(mContext, mMainView, HORIZONTAL);
- mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
+ mSwipeDetector = new SingleAxisSwipeDetector(mContext, mMainView, HORIZONTAL);
+ mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_BOTH, false);
mMainView.setSwipeDetector(mSwipeDetector);
mFooter.setContainer(this);
}
diff --git a/src/com/android/launcher3/notification/NotificationKeyData.java b/src/com/android/launcher3/notification/NotificationKeyData.java
index 5050457..a1917ec 100644
--- a/src/com/android/launcher3/notification/NotificationKeyData.java
+++ b/src/com/android/launcher3/notification/NotificationKeyData.java
@@ -17,13 +17,17 @@
package com.android.launcher3.notification;
import android.app.Notification;
+import android.app.Person;
import android.service.notification.StatusBarNotification;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.Utilities;
+
import java.util.ArrayList;
import java.util.List;
-import androidx.annotation.NonNull;
-
/**
* The key data associated with the notification, used to determine what to include
* in dots and dummy popup views before they are populated.
@@ -33,20 +37,27 @@
public class NotificationKeyData {
public final String notificationKey;
public final String shortcutId;
+ @NonNull
+ public final String[] personKeysFromNotification;
public int count;
- private NotificationKeyData(String notificationKey, String shortcutId, int count) {
+ private NotificationKeyData(String notificationKey, String shortcutId, int count,
+ String[] personKeysFromNotification) {
this.notificationKey = notificationKey;
this.shortcutId = shortcutId;
this.count = Math.max(1, count);
+ this.personKeysFromNotification = personKeysFromNotification;
}
public static NotificationKeyData fromNotification(StatusBarNotification sbn) {
Notification notif = sbn.getNotification();
- return new NotificationKeyData(sbn.getKey(), notif.getShortcutId(), notif.number);
+ return new NotificationKeyData(sbn.getKey(), notif.getShortcutId(), notif.number,
+ extractPersonKeyOnly(notif.extras.getParcelableArrayList(
+ Notification.EXTRA_PEOPLE_LIST)));
}
- public static List<String> extractKeysOnly(@NonNull List<NotificationKeyData> notificationKeys) {
+ public static List<String> extractKeysOnly(
+ @NonNull List<NotificationKeyData> notificationKeys) {
List<String> keysOnly = new ArrayList<>(notificationKeys.size());
for (NotificationKeyData notificationKeyData : notificationKeys) {
keysOnly.add(notificationKeyData.notificationKey);
@@ -54,6 +65,14 @@
return keysOnly;
}
+ private static String[] extractPersonKeyOnly(@Nullable ArrayList<Person> people) {
+ if (people == null || people.isEmpty()) {
+ return Utilities.EMPTY_STRING_ARRAY;
+ }
+ return people.stream().filter(person -> person.getKey() != null)
+ .map(Person::getKey).sorted().toArray(String[]::new);
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof NotificationKeyData)) {
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index e57a051..10378ee 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -16,6 +16,7 @@
package com.android.launcher3.notification;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
import android.annotation.TargetApi;
@@ -31,7 +32,8 @@
import android.util.Log;
import android.util.Pair;
-import com.android.launcher3.LauncherModel;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.SecureSettingsObserver;
@@ -43,8 +45,6 @@
import java.util.List;
import java.util.Map;
-import androidx.annotation.Nullable;
-
/**
* A {@link NotificationListenerService} that sends updates to its
* {@link NotificationsChangedListener} when notifications are posted or canceled,
@@ -141,7 +141,7 @@
public NotificationListener() {
super();
- mWorkerHandler = new Handler(LauncherModel.getWorkerLooper(), mWorkerCallback);
+ mWorkerHandler = new Handler(MODEL_EXECUTOR.getLooper(), mWorkerCallback);
mUiHandler = new Handler(Looper.getMainLooper(), mUiCallback);
sNotificationListenerInstance = this;
}
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index 78627ec..b67adbb 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -38,8 +38,9 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.OverScroll;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.Themes;
@@ -48,7 +49,7 @@
* e.g. icon + title + text.
*/
@TargetApi(Build.VERSION_CODES.N)
-public class NotificationMainView extends FrameLayout implements SwipeDetector.Listener {
+public class NotificationMainView extends FrameLayout implements SingleAxisSwipeDetector.Listener {
private static FloatProperty<NotificationMainView> CONTENT_TRANSLATION =
new FloatProperty<NotificationMainView>("contentTranslation") {
@@ -75,7 +76,7 @@
private TextView mTextView;
private View mIconView;
- private SwipeDetector mSwipeDetector;
+ private SingleAxisSwipeDetector mSwipeDetector;
public NotificationMainView(Context context) {
this(context, null, 0);
@@ -107,7 +108,7 @@
mIconView = findViewById(R.id.popup_item_icon);
}
- public void setSwipeDetector(SwipeDetector swipeDetector) {
+ public void setSwipeDetector(SingleAxisSwipeDetector swipeDetector) {
mSwipeDetector = swipeDetector;
}
@@ -173,7 +174,7 @@
LauncherLogProto.ItemType.NOTIFICATION);
}
- // SwipeDetector.Listener's
+ // SingleAxisSwipeDetector.Listener's
@Override
public void onDragStart(boolean start) { }
@@ -187,7 +188,7 @@
}
@Override
- public void onDragEnd(float velocity, boolean fling) {
+ public void onDragEnd(float velocity) {
final boolean willExit;
final float endTranslation;
final float startTranslation = mTextAndBackground.getTranslationX();
@@ -195,7 +196,7 @@
if (!canChildBeDismissed()) {
willExit = false;
endTranslation = 0;
- } else if (fling) {
+ } else if (mSwipeDetector.isFling(velocity)) {
willExit = true;
endTranslation = velocity < 0 ? - getWidth() : getWidth();
} else if (Math.abs(startTranslation) > getWidth() / 2) {
@@ -206,7 +207,7 @@
endTranslation = 0;
}
- long duration = SwipeDetector.calculateDuration(velocity,
+ long duration = BaseSwipeDetector.calculateDuration(velocity,
(endTranslation - startTranslation) / getWidth());
mContentTranslateAnimator.removeAllListeners();
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 28000b9..98f7fd8 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -360,10 +360,14 @@
final TimeInterpolator revealInterpolator = ACCEL_DEACCEL;
// Rectangular reveal.
+ mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
.createRevealAnimator(this, false);
revealAnim.setDuration(revealDuration);
revealAnim.setInterpolator(revealInterpolator);
+ // Clip the popup to the initial outline while the notification dot and arrow animate.
+ revealAnim.start();
+ revealAnim.pause();
ValueAnimator fadeIn = ValueAnimator.ofFloat(0, 1);
fadeIn.setDuration(revealDuration + arrowDuration);
@@ -399,7 +403,6 @@
if (!mIsOpen) {
return;
}
- mEndRect.setEmpty();
if (getOutlineProvider() instanceof RevealOutlineAnimation) {
((RevealOutlineAnimation) getOutlineProvider()).getOutline(mEndRect);
}
@@ -471,9 +474,6 @@
mStartRect.set(arrowCenterX - halfArrowWidth, arrowCenterY, arrowCenterX + halfArrowWidth,
arrowCenterY);
- if (mEndRect.isEmpty()) {
- mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
- }
return new RoundedRectRevealOutlineProvider
(arrowCornerRadius, mOutlineRadius, mStartRect, mEndRect);
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 9719a18..e8ac1d4 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -24,6 +24,7 @@
import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.animation.AnimatorSet;
import android.animation.LayoutTransition;
@@ -51,9 +52,7 @@
import com.android.launcher3.ItemInfo;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherModel;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
import com.android.launcher3.dot.DotInfo;
@@ -65,13 +64,13 @@
import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.notification.NotificationKeyData;
import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.views.BaseDragLayer;
import java.util.ArrayList;
@@ -169,7 +168,10 @@
}
public OnClickListener getItemClickListener() {
- return ItemClickHandler.INSTANCE;
+ return (view) -> {
+ ItemClickHandler.INSTANCE.onClick(view);
+ close(true);
+ };
}
@Override
@@ -194,6 +196,9 @@
* @return the container if shown or null.
*/
public static PopupContainerWithArrow showForIcon(BubbleTextView icon) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_CONTEXT_MENU, "showForIcon");
+ }
Launcher launcher = Launcher.getLauncher(icon.getContext());
if (getOpen(launcher) != null) {
// There is already an items container open, so don't open this one.
@@ -201,7 +206,7 @@
return null;
}
ItemInfo itemInfo = (ItemInfo) icon.getTag();
- if (!DeepShortcutManager.supportsShortcuts(itemInfo)) {
+ if (!ShortcutUtil.supportsShortcuts(itemInfo)) {
return null;
}
@@ -235,6 +240,9 @@
protected void populateAndShow(
BubbleTextView icon, ItemInfo item, SystemShortcutFactory factory) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_CONTEXT_MENU, "populateAndShow");
+ }
PopupDataProvider popupDataProvider = mLauncher.getPopupDataProvider();
populateAndShow(icon,
popupDataProvider.getShortcutCountForItem(item),
@@ -300,14 +308,13 @@
}
mLauncher.getDragController().addDragListener(this);
- mOriginalIcon.forceHideDot(true);
+ mOriginalIcon.setForceHideDot(true);
// All views are added. Animate layout from now on.
setLayoutTransition(new LayoutTransition());
// Load the shortcuts on a background thread and update the container as it animates.
- final Looper workerLooper = LauncherModel.getWorkerLooper();
- new Handler(workerLooper).postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
+ MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()),
this, mShortcuts, notificationKeys));
}
@@ -449,11 +456,6 @@
@Override
public boolean shouldStartDrag(double distanceDragged) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_DRAG_TAG,
- "createPreDragCondition().shouldStartDrag " + distanceDragged + ", "
- + mStartDragThreshold);
- }
return distanceDragged > mStartDragThreshold;
}
@@ -568,14 +570,17 @@
protected void onCreateCloseAnimation(AnimatorSet anim) {
// Animate original icon's text back in.
anim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */));
- mOriginalIcon.forceHideDot(false);
+ mOriginalIcon.setForceHideDot(false);
}
@Override
protected void closeComplete() {
+ PopupContainerWithArrow openPopup = getOpen(mLauncher);
+ if (openPopup == null || openPopup.mOriginalIcon != mOriginalIcon) {
+ mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
+ mOriginalIcon.setForceHideDot(false);
+ }
super.closeComplete();
- mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
- mOriginalIcon.forceHideDot(false);
}
@Override
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 2d301ac..4612b2a 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -29,17 +29,22 @@
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.widget.WidgetListRowEntry;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
/**
* Provides data for the popup menu that appears after long-clicking on apps.
@@ -129,7 +134,8 @@
for (PackageUserKey packageUserKey : mPackageUserToDotInfos.keySet()) {
DotInfo prevDot = updatedDots.get(packageUserKey);
DotInfo newDot = mPackageUserToDotInfos.get(packageUserKey);
- if (prevDot == null) {
+ if (prevDot == null
+ || prevDot.getNotificationCount() != newDot.getNotificationCount()) {
updatedDots.put(packageUserKey, newDot);
} else {
// No need to update the dot if it already existed (no visual change).
@@ -155,7 +161,7 @@
}
public int getShortcutCountForItem(ItemInfo info) {
- if (!DeepShortcutManager.supportsShortcuts(info)) {
+ if (!ShortcutUtil.supportsDeepShortcuts(info)) {
return 0;
}
ComponentName component = info.getTargetComponent();
@@ -167,17 +173,26 @@
return count == null ? 0 : count;
}
- public DotInfo getDotInfoForItem(ItemInfo info) {
- if (!DeepShortcutManager.supportsShortcuts(info)) {
+ public @Nullable DotInfo getDotInfoForItem(@NonNull ItemInfo info) {
+ if (!ShortcutUtil.supportsShortcuts(info)) {
return null;
}
-
- return mPackageUserToDotInfos.get(PackageUserKey.fromItemInfo(info));
+ DotInfo dotInfo = mPackageUserToDotInfos.get(PackageUserKey.fromItemInfo(info));
+ if (dotInfo == null) {
+ return null;
+ }
+ List<NotificationKeyData> notifications = getNotificationsForItem(
+ info, dotInfo.getNotificationKeys());
+ if (notifications.isEmpty()) {
+ return null;
+ }
+ return dotInfo;
}
public @NonNull List<NotificationKeyData> getNotificationKeysForItem(ItemInfo info) {
DotInfo dotInfo = getDotInfoForItem(info);
- return dotInfo == null ? Collections.EMPTY_LIST : dotInfo.getNotificationKeys();
+ return dotInfo == null ? Collections.EMPTY_LIST
+ : getNotificationsForItem(info, dotInfo.getNotificationKeys());
}
/** This makes a potentially expensive binder call and should be run on a background thread. */
@@ -226,6 +241,27 @@
return null;
}
+ /**
+ * Returns a list of notifications that are relevant to given ItemInfo.
+ */
+ public static @NonNull List<NotificationKeyData> getNotificationsForItem(
+ @NonNull ItemInfo info, @NonNull List<NotificationKeyData> notifications) {
+ String shortcutId = ShortcutUtil.getShortcutIdIfPinnedShortcut(info);
+ if (shortcutId == null) {
+ return notifications;
+ }
+ String[] personKeys = ShortcutUtil.getPersonKeysIfPinnedShortcut(info);
+ return notifications.stream().filter((NotificationKeyData notification) -> {
+ if (notification.shortcutId != null) {
+ return notification.shortcutId.equals(shortcutId);
+ }
+ if (notification.personKeysFromNotification.length != 0) {
+ return Arrays.equals(notification.personKeysFromNotification, personKeys);
+ }
+ return false;
+ }).collect(Collectors.toList());
+ }
+
public interface PopupDataChangeListener {
PopupDataChangeListener INSTANCE = new PopupDataChangeListener() { };
diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java
index 41ab4df..5a5fbab 100644
--- a/src/com/android/launcher3/popup/RemoteActionShortcut.java
+++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java
@@ -29,11 +29,12 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.userevent.nano.LauncherLogProto;
public class RemoteActionShortcut extends SystemShortcut<BaseDraggingActivity> {
private static final String TAG = "RemoteActionShortcut";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = Utilities.IS_DEBUG_DEVICE;
private final RemoteAction mAction;
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 563f3b3..a87b7b8 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -1,14 +1,11 @@
package com.android.launcher3.popup;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.graphics.drawable.Icon;
-import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
@@ -20,23 +17,30 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.WidgetsBottomSheet;
import java.util.List;
-
/**
* Represents a system shortcut for a given app. The shortcut should have a label and icon, and an
* onClickListener that depends on the item that the shortcut services.
*
* Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
+ * @param <T>
*/
-public abstract class SystemShortcut<T extends BaseDraggingActivity> extends ItemInfo {
+public abstract class SystemShortcut<T extends BaseDraggingActivity>
+ extends ItemInfo {
private final int mIconResId;
private final int mLabelResId;
private final Icon mIcon;
@@ -131,6 +135,7 @@
@Override
public View.OnClickListener getOnClickListener(final Launcher launcher,
final ItemInfo itemInfo) {
+ if (itemInfo.getTargetComponent() == null) return null;
final List<WidgetItem> widgets =
launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
@@ -201,6 +206,27 @@
}
}
+ public static class DismissPrediction extends SystemShortcut<Launcher> {
+ public DismissPrediction() {
+ super(R.drawable.ic_remove_no_shadow, R.string.dismiss_prediction_label);
+ }
+
+ @Override
+ public View.OnClickListener getOnClickListener(Launcher activity, ItemInfo itemInfo) {
+ if (!FeatureFlags.ENABLE_PREDICTION_DISMISS.get()) return null;
+ if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) return null;
+ return (view) -> {
+ PopupContainerWithArrow.closeAllOpenViews(activity);
+ activity.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
+ ControlType.DISMISS_PREDICTION, ContainerType.DEEPSHORTCUTS);
+ AppLaunchTracker.INSTANCE.get(view.getContext())
+ .onDismissApp(itemInfo.getTargetComponent(),
+ itemInfo.user,
+ AppLaunchTracker.CONTAINER_PREDICTIONS);
+ };
+ }
+ }
+
protected static void dismissTaskMenuView(BaseDraggingActivity activity) {
AbstractFloatingView.closeOpenViews(activity, true,
AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
diff --git a/src/com/android/launcher3/popup/SystemShortcutFactory.java b/src/com/android/launcher3/popup/SystemShortcutFactory.java
index 516fafa..dfcc2f8 100644
--- a/src/com/android/launcher3/popup/SystemShortcutFactory.java
+++ b/src/com/android/launcher3/popup/SystemShortcutFactory.java
@@ -15,6 +15,10 @@
*/
package com.android.launcher3.popup;
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+
+import androidx.annotation.NonNull;
+
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
@@ -24,13 +28,10 @@
import java.util.ArrayList;
import java.util.List;
-import androidx.annotation.NonNull;
-
public class SystemShortcutFactory implements ResourceBasedOverride {
public static final MainThreadInitializedObject<SystemShortcutFactory> INSTANCE =
- new MainThreadInitializedObject<>(c -> Overrides.getObject(
- SystemShortcutFactory.class, c, R.string.system_shortcut_factory_class));
+ forOverride(SystemShortcutFactory.class, R.string.system_shortcut_factory_class);
/** Note that these are in order of priority. */
private final SystemShortcut[] mAllShortcuts;
@@ -38,7 +39,9 @@
@SuppressWarnings("unused")
public SystemShortcutFactory() {
this(new SystemShortcut.AppInfo(),
- new SystemShortcut.Widgets(), new SystemShortcut.Install());
+ new SystemShortcut.Widgets(),
+ new SystemShortcut.Install(),
+ new SystemShortcut.DismissPrediction());
}
protected SystemShortcutFactory(SystemShortcut... shortcuts) {
@@ -52,6 +55,7 @@
systemShortcuts.add(systemShortcut);
}
}
+
return systemShortcuts;
}
}
diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java
index 7b62f53..970a03e 100644
--- a/src/com/android/launcher3/provider/ImportDataTask.java
+++ b/src/com/android/launcher3/provider/ImportDataTask.java
@@ -42,7 +42,6 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.Settings;
-import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
@@ -50,6 +49,7 @@
import com.android.launcher3.model.GridSizeMigrationTask;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.PackageManagerHelper;
import java.net.URISyntaxException;
import java.util.ArrayList;
@@ -223,7 +223,7 @@
case Favorites.ITEM_TYPE_SHORTCUT:
case Favorites.ITEM_TYPE_APPLICATION: {
intent = Intent.parseUri(c.getString(intentIndex), 0);
- if (Utilities.isLauncherAppTarget(intent)) {
+ if (PackageManagerHelper.isLauncherAppTarget(intent)) {
type = Favorites.ITEM_TYPE_APPLICATION;
} else {
values.put(Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 3c0c5fd..fb33551 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -16,8 +16,6 @@
package com.android.launcher3.provider;
-import static com.android.launcher3.Utilities.getIntArrayFromString;
-import static com.android.launcher3.Utilities.getStringFromIntArray;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
import android.app.backup.BackupManager;
@@ -40,6 +38,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
+import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.LogConfig;
import java.io.InvalidObjectException;
@@ -173,12 +172,6 @@
values.put(Favorites.PROFILE_ID, newProfileId);
db.update(Favorites.TABLE_NAME, values, "profileId = ?",
new String[]{Long.toString(oldProfileId)});
-
- // Change default value of the column.
- db.execSQL("ALTER TABLE favorites RENAME TO favorites_old;");
- Favorites.addTableToDb(db, newProfileId, false);
- db.execSQL("INSERT INTO favorites SELECT * FROM favorites_old;");
- dropTable(db, "favorites_old");
}
@@ -246,8 +239,8 @@
SharedPreferences prefs = Utilities.getPrefs(context);
if (prefs.contains(APPWIDGET_OLD_IDS) && prefs.contains(APPWIDGET_IDS)) {
AppWidgetsRestoredReceiver.restoreAppWidgetIds(context,
- getIntArrayFromString(prefs.getString(APPWIDGET_OLD_IDS, "")),
- getIntArrayFromString(prefs.getString(APPWIDGET_IDS, "")));
+ IntArray.fromConcatString(prefs.getString(APPWIDGET_OLD_IDS, "")).toArray(),
+ IntArray.fromConcatString(prefs.getString(APPWIDGET_IDS, "")).toArray());
} else {
FileLog.d(TAG, "No app widget ids to restore.");
}
@@ -259,8 +252,8 @@
public static void setRestoredAppWidgetIds(Context context, @NonNull int[] oldIds,
@NonNull int[] newIds) {
Utilities.getPrefs(context).edit()
- .putString(APPWIDGET_OLD_IDS, getStringFromIntArray(oldIds))
- .putString(APPWIDGET_IDS, getStringFromIntArray(newIds))
+ .putString(APPWIDGET_OLD_IDS, IntArray.wrap(oldIds).toConcatString())
+ .putString(APPWIDGET_IDS, IntArray.wrap(newIds).toConcatString())
.commit();
}
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index 857ea05..0eb4285 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -32,12 +32,16 @@
import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
+import android.provider.Settings;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.AppWidgetResizeFrame;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
@@ -55,6 +59,74 @@
*/
public class QsbContainerView extends FrameLayout {
+ public static final String SEARCH_PROVIDER_SETTINGS_KEY = "SEARCH_PROVIDER_PACKAGE_NAME";
+
+ /**
+ * Returns the package name for user configured search provider or from searchManager
+ * @param context
+ * @return String
+ */
+ @Nullable
+ public static String getSearchWidgetPackageName(@NonNull Context context) {
+ String providerPkg = Settings.Global.getString(context.getContentResolver(),
+ SEARCH_PROVIDER_SETTINGS_KEY);
+ if (providerPkg == null) {
+ SearchManager searchManager = context.getSystemService(SearchManager.class);
+ ComponentName componentName = searchManager.getGlobalSearchActivity();
+ if (componentName != null) {
+ providerPkg = searchManager.getGlobalSearchActivity().getPackageName();
+ }
+ }
+ return providerPkg;
+ }
+
+ /**
+ * returns it's AppWidgetProviderInfo using package name from getSearchWidgetPackageName
+ * @param context
+ * @return AppWidgetProviderInfo
+ */
+ @Nullable
+ public static AppWidgetProviderInfo getSearchWidgetProviderInfo(@NonNull Context context) {
+ String providerPkg = getSearchWidgetPackageName(context);
+ if (providerPkg == null) {
+ return null;
+ }
+
+ AppWidgetProviderInfo defaultWidgetForSearchPackage = null;
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ for (AppWidgetProviderInfo info :
+ appWidgetManager.getInstalledProvidersForPackage(providerPkg, null)) {
+ if (info.provider.getPackageName().equals(providerPkg) && info.configure == null) {
+ if ((info.widgetCategory
+ & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
+ return info;
+ } else if (defaultWidgetForSearchPackage == null) {
+ defaultWidgetForSearchPackage = info;
+ }
+ }
+ }
+ return defaultWidgetForSearchPackage;
+ }
+
+ /**
+ * returns componentName for searchWidget if package name is known.
+ */
+ @Nullable
+ public static ComponentName getSearchComponentName(@NonNull Context context) {
+ AppWidgetProviderInfo providerInfo =
+ QsbContainerView.getSearchWidgetProviderInfo(context);
+ if (providerInfo != null) {
+ return providerInfo.provider;
+ } else {
+ String pkgName = QsbContainerView.getSearchWidgetPackageName(context);
+ if (pkgName != null) {
+ //we don't know the class name yet. we'll put the package name as placeholder
+ return new ComponentName(pkgName, pkgName);
+ }
+ return null;
+ }
+ }
+
public QsbContainerView(Context context) {
super(context);
}
@@ -101,7 +173,7 @@
protected QsbWidgetHost createHost() {
return new QsbWidgetHost(getContext(), QSB_WIDGET_HOST_ID,
- (c) -> new QsbWidgetHostView(c));
+ (c) -> new QsbWidgetHostView(c), this::rebindFragment);
}
private FrameLayout mWrapper;
@@ -111,9 +183,9 @@
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mWrapper = new FrameLayout(getContext());
-
// Only add the view when enabled
if (isQsbEnabled()) {
+ mQsbWidgetHost.startListening();
mWrapper.addView(createQsb(mWrapper));
}
return mWrapper;
@@ -155,7 +227,8 @@
}
if (isWidgetBound) {
- mQsb = (QsbWidgetHostView) mQsbWidgetHost.createView(context, widgetId, mWidgetInfo);
+ mQsb = (QsbWidgetHostView) mQsbWidgetHost.createView(context, widgetId,
+ mWidgetInfo);
mQsb.setId(R.id.qsb_widget);
if (!isInPreviewMode()) {
@@ -163,7 +236,6 @@
.getAppWidgetOptions(widgetId), opts)) {
mQsb.updateAppWidgetOptions(opts);
}
- mQsbWidgetHost.startListening();
}
return mQsb;
}
@@ -246,43 +318,32 @@
return v;
}
+
/**
* Returns a widget with category {@link AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX}
- * provided by the same package which is set to be global search activity.
+ * provided by the package from getSearchProviderPackageName
* If widgetCategory is not supported, or no such widget is found, returns the first widget
* provided by the package.
*/
protected AppWidgetProviderInfo getSearchWidgetProvider() {
- SearchManager searchManager =
- (SearchManager) getContext().getSystemService(Context.SEARCH_SERVICE);
- ComponentName searchComponent = searchManager.getGlobalSearchActivity();
- if (searchComponent == null) return null;
- String providerPkg = searchComponent.getPackageName();
-
- AppWidgetProviderInfo defaultWidgetForSearchPackage = null;
-
- AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getContext());
- for (AppWidgetProviderInfo info : appWidgetManager.getInstalledProviders()) {
- if (info.provider.getPackageName().equals(providerPkg) && info.configure == null) {
- if ((info.widgetCategory
- & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
- return info;
- } else if (defaultWidgetForSearchPackage == null) {
- defaultWidgetForSearchPackage = info;
- }
- }
- }
- return defaultWidgetForSearchPackage;
+ return getSearchWidgetProviderInfo(getContext());
}
}
public static class QsbWidgetHost extends AppWidgetHost {
private final WidgetViewFactory mViewFactory;
+ private final WidgetProvidersUpdateCallback mWidgetsUpdateCallback;
- public QsbWidgetHost(Context context, int hostId, WidgetViewFactory viewFactory) {
+ public QsbWidgetHost(Context context, int hostId, WidgetViewFactory viewFactory,
+ WidgetProvidersUpdateCallback widgetProvidersUpdateCallback) {
super(context, hostId);
mViewFactory = viewFactory;
+ mWidgetsUpdateCallback = widgetProvidersUpdateCallback;
+ }
+
+ public QsbWidgetHost(Context context, int hostId, WidgetViewFactory viewFactory) {
+ this(context, hostId, viewFactory, null);
}
@Override
@@ -290,6 +351,14 @@
Context context, int appWidgetId, AppWidgetProviderInfo appWidget) {
return mViewFactory.newView(context);
}
+
+ @Override
+ protected void onProvidersChanged() {
+ super.onProvidersChanged();
+ if (mWidgetsUpdateCallback != null) {
+ mWidgetsUpdateCallback.onProvidersUpdated();
+ }
+ }
}
public interface WidgetViewFactory {
@@ -298,6 +367,17 @@
}
/**
+ * Callback interface for packages list update.
+ */
+ @FunctionalInterface
+ public interface WidgetProvidersUpdateCallback {
+ /**
+ * Gets called when widget providers list changes
+ */
+ void onProvidersUpdated();
+ }
+
+ /**
* Returns true if {@param original} contains all entries defined in {@param updates} and
* have the same value.
* The comparison uses {@link Object#equals(Object)} to compare the values.
@@ -316,4 +396,5 @@
}
return true;
}
+
}
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 18b6094..8d2db78 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -16,6 +16,8 @@
package com.android.launcher3.settings;
+import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
+
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;
@@ -32,14 +34,7 @@
import android.provider.Settings;
import android.text.TextUtils;
-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.GridOptionsProvider;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-import com.android.launcher3.util.SecureSettingsObserver;
-
+import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragment;
import androidx.preference.PreferenceFragment.OnPreferenceStartFragmentCallback;
@@ -48,6 +43,14 @@
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;
+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.GridOptionsProvider;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.SecureSettingsObserver;
+
/**
* Settings activity for Launcher. Currently implements the following setting: Allow rotation
*/
@@ -248,6 +251,8 @@
if (highlighter != null) {
getView().postDelayed(highlighter, DELAY_HIGHLIGHT_DURATION_MILLIS);
mPreferenceHighlighted = true;
+ } else {
+ requestAccessibilityFocus(getListView());
}
}
}
@@ -268,6 +273,15 @@
return position >= 0 ? new PreferenceHighlighter(list, position) : null;
}
+ private void requestAccessibilityFocus(@NonNull final RecyclerView rv) {
+ rv.post(() -> {
+ if (!rv.hasFocus() && rv.getChildCount() > 0) {
+ rv.getChildAt(0)
+ .performAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, null);
+ }
+ });
+ }
+
@Override
public void onDestroy() {
if (mNotificationDotsObserver != null) {
diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java
index c6370c5..a23cd6d 100644
--- a/src/com/android/launcher3/states/InternalStateHandler.java
+++ b/src/com/android/launcher3/states/InternalStateHandler.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.states;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
@@ -22,8 +24,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel.Callbacks;
-import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.model.BgDataModel.Callbacks;
import java.lang.ref.WeakReference;
@@ -94,16 +95,12 @@
private static class Scheduler implements Runnable {
private WeakReference<InternalStateHandler> mPendingHandler = new WeakReference<>(null);
- private MainThreadExecutor mMainThreadExecutor;
public void schedule(InternalStateHandler handler) {
synchronized (this) {
mPendingHandler = new WeakReference<>(handler);
- if (mMainThreadExecutor == null) {
- mMainThreadExecutor = new MainThreadExecutor();
- }
}
- mMainThreadExecutor.execute(this);
+ MAIN_EXECUTOR.execute(this);
}
@Override
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index d2e1961..c6de9ca 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -15,19 +15,27 @@
*/
package com.android.launcher3.testing;
+import static android.graphics.Bitmap.Config.ARGB_8888;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
import android.os.Bundle;
+import android.os.Debug;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.util.ResourceBasedOverride;
+import java.util.LinkedList;
import java.util.concurrent.ExecutionException;
public class TestInformationHandler implements ResourceBasedOverride {
@@ -41,6 +49,7 @@
protected DeviceProfile mDeviceProfile;
protected LauncherAppState mLauncherAppState;
protected Launcher mLauncher;
+ private static LinkedList mLeaks;
public void init(Context context) {
mContext = context;
@@ -74,6 +83,11 @@
break;
}
+ case TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED: {
+ response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, isLauncherInitialized());
+ break;
+ }
+
case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
TestProtocol.sDebugTracing = true;
break;
@@ -83,20 +97,20 @@
break;
case TestProtocol.REQUEST_FREEZE_APP_LIST:
- new MainThreadExecutor().execute(() ->
+ MAIN_EXECUTOR.execute(() ->
mLauncher.getAppsView().getAppsStore().enableDeferUpdates(
AllAppsStore.DEFER_UPDATES_TEST));
break;
case TestProtocol.REQUEST_UNFREEZE_APP_LIST:
- new MainThreadExecutor().execute(() ->
+ MAIN_EXECUTOR.execute(() ->
mLauncher.getAppsView().getAppsStore().disableDeferUpdates(
AllAppsStore.DEFER_UPDATES_TEST));
break;
case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
try {
- final int deferUpdatesFlags = new MainThreadExecutor().submit(() ->
+ final int deferUpdatesFlags = MAIN_EXECUTOR.submit(() ->
mLauncher.getAppsView().getAppsStore().getDeferUpdatesFlags()).get();
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
deferUpdatesFlags);
@@ -107,7 +121,49 @@
}
break;
}
+
+ case TestProtocol.REQUEST_TOTAL_PSS_KB: {
+ Debug.MemoryInfo mem = new Debug.MemoryInfo();
+ Debug.getMemoryInfo(mem);
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, mem.getTotalPss());
+ break;
+ }
+
+ case TestProtocol.REQUEST_JAVA_LEAK: {
+ if (mLeaks == null) mLeaks = new LinkedList();
+
+ // Allocate and dirty the memory.
+ final int leakSize = 1024 * 1024;
+ final byte[] bytes = new byte[leakSize];
+ for (int i = 0; i < leakSize; i += 239) {
+ bytes[i] = (byte) (i % 256);
+ }
+ mLeaks.add(bytes);
+ break;
+ }
+
+ case TestProtocol.REQUEST_NATIVE_LEAK: {
+ if (mLeaks == null) mLeaks = new LinkedList();
+
+ // Allocate and dirty a bitmap.
+ final Bitmap bitmap = Bitmap.createBitmap(512, 512, ARGB_8888);
+ bitmap.eraseColor(Color.RED);
+ mLeaks.add(bitmap);
+ break;
+ }
+
+ case TestProtocol.REQUEST_ICON_HEIGHT: {
+ response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+ mDeviceProfile.allAppsCellHeightPx);
+ break;
+ }
}
return response;
}
+
+ protected boolean isLauncherInitialized() {
+ final LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
+ return model.getCallback() == null || model.isModelLoaded();
+ }
}
+
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 6ffc2d9..07ddbdc 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -66,16 +66,25 @@
"all-apps-to-overview-swipe-height";
public static final String REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT =
"home-to-all-apps-swipe-height";
+ public static final String REQUEST_ICON_HEIGHT =
+ "icon-height";
+ public static final String REQUEST_HOTSEAT_TOP = "hotseat-top";
+ public static final String REQUEST_IS_LAUNCHER_INITIALIZED = "is-launcher-initialized";
public static final String REQUEST_FREEZE_APP_LIST = "freeze-app-list";
public static final String REQUEST_UNFREEZE_APP_LIST = "unfreeze-app-list";
public static final String REQUEST_APP_LIST_FREEZE_FLAGS = "app-list-freeze-flags";
+ public static final String REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN = "overview-left-margin";
+ public static final String REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN = "overview-right-margin";
+ public static final String REQUEST_TOTAL_PSS_KB = "total_pss";
+ public static final String REQUEST_JAVA_LEAK = "java-leak";
+ public static final String REQUEST_NATIVE_LEAK = "native-leak";
public static boolean sDebugTracing = false;
public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
public static final String REQUEST_DISABLE_DEBUG_TRACING = "disable-debug-tracing";
- public static final String NO_ALLAPPS_EVENT_TAG = "b/133867119";
- public static final String NO_DRAG_TAG = "b/133009122";
- public static final String NO_START_TAG = "b/132900132";
- public static final String NO_START_TASK_TAG = "b/133765434";
- public static final String NO_OVERVIEW_EVENT_TAG = "b/134532571";
+
+ public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824";
+ public static final String NO_DRAG_TO_WORKSPACE = "b/138729456";
+ public static final String APP_NOT_DISABLED = "b/139891609";
+ public static final String NO_CONTEXT_MENU = "b/141770616";
}
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 6f53140..f40f976 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -22,16 +22,15 @@
import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_SCALE_COMPONENT;
import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.os.SystemClock;
-import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
@@ -43,7 +42,6 @@
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -55,9 +53,7 @@
* TouchController for handling state changes
*/
public abstract class AbstractStateChangeTouchController
- implements TouchController, SwipeDetector.Listener {
-
- private static final String TAG = "ASCTouchController";
+ implements TouchController, SingleAxisSwipeDetector.Listener {
// Progress after which the transition is assumed to be a success in case user does not fling
public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
@@ -69,10 +65,11 @@
protected final long ATOMIC_DURATION = getAtomicDuration();
protected final Launcher mLauncher;
- protected final SwipeDetector mDetector;
- protected final SwipeDetector.Direction mSwipeDirection;
+ protected final SingleAxisSwipeDetector mDetector;
+ protected final SingleAxisSwipeDetector.Direction mSwipeDirection;
private boolean mNoIntercept;
+ private boolean mIsLogContainerSet;
protected int mStartContainerType;
protected LauncherState mStartState;
@@ -104,9 +101,9 @@
private float mAtomicComponentsStartProgress;
- public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) {
+ public AbstractStateChangeTouchController(Launcher l, SingleAxisSwipeDetector.Direction dir) {
mLauncher = l;
- mDetector = new SwipeDetector(l, this, dir);
+ mDetector = new SingleAxisSwipeDetector(l, this, dir);
mSwipeDirection = dir;
}
@@ -118,9 +115,6 @@
@Override
public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onControllerInterceptTouchEvent 1 " + ev);
- }
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = !canInterceptTouch(ev);
if (mNoIntercept) {
@@ -133,7 +127,7 @@
boolean ignoreSlopWhenSettling = false;
if (mCurrentAnimation != null) {
- directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+ directionsToDetectScroll = SingleAxisSwipeDetector.DIRECTION_BOTH;
ignoreSlopWhenSettling = true;
} else {
directionsToDetectScroll = getSwipeDirection();
@@ -150,9 +144,6 @@
return false;
}
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onControllerInterceptTouchEvent 2 ");
- }
onControllerTouchEvent(ev);
return mDetector.isDraggingOrSettling();
}
@@ -161,10 +152,10 @@
LauncherState fromState = mLauncher.getStateManager().getState();
int swipeDirection = 0;
if (getTargetState(fromState, true /* isDragTowardPositive */) != fromState) {
- swipeDirection |= SwipeDetector.DIRECTION_POSITIVE;
+ swipeDirection |= SingleAxisSwipeDetector.DIRECTION_POSITIVE;
}
if (getTargetState(fromState, false /* isDragTowardPositive */) != fromState) {
- swipeDirection |= SwipeDetector.DIRECTION_NEGATIVE;
+ swipeDirection |= SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
}
return swipeDirection;
}
@@ -190,7 +181,7 @@
/**
* Returns the container that the touch started from when leaving NORMAL state.
*/
- protected abstract int getLogContainerTypeForNormalState();
+ protected abstract int getLogContainerTypeForNormalState(MotionEvent ev);
private boolean reinitCurrentAnimation(boolean reachedToState, boolean isDragTowardPositive) {
LauncherState newFromState = mFromState == null ? mLauncher.getStateManager().getState()
@@ -240,21 +231,9 @@
@Override
public void onDragStart(boolean start) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onDragStart 1 " + start);
- }
mStartState = mLauncher.getStateManager().getState();
- if (mStartState == ALL_APPS) {
- mStartContainerType = LauncherLogProto.ContainerType.ALLAPPS;
- } else if (mStartState == NORMAL) {
- mStartContainerType = getLogContainerTypeForNormalState();
- } else if (mStartState == OVERVIEW){
- mStartContainerType = LauncherLogProto.ContainerType.TASKSWITCHER;
- }
+ mIsLogContainerSet = false;
if (mCurrentAnimation == null) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onDragStart 2");
- }
mFromState = mStartState;
mToState = null;
cancelAnimationControllers();
@@ -301,6 +280,21 @@
return true;
}
+ @Override
+ public boolean onDrag(float displacement, MotionEvent ev) {
+ if (!mIsLogContainerSet) {
+ if (mStartState == ALL_APPS) {
+ mStartContainerType = LauncherLogProto.ContainerType.ALLAPPS;
+ } else if (mStartState == NORMAL) {
+ mStartContainerType = getLogContainerTypeForNormalState(ev);
+ } else if (mStartState == OVERVIEW) {
+ mStartContainerType = LauncherLogProto.ContainerType.TASKSWITCHER;
+ }
+ mIsLogContainerSet = true;
+ }
+ return onDrag(displacement);
+ }
+
protected void updateProgress(float fraction) {
mCurrentAnimation.setPlayFraction(fraction);
if (mAtomicComponentsController != null) {
@@ -375,10 +369,8 @@
}
@Override
- public void onDragEnd(float velocity, boolean fling) {
- if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
- android.util.Log.e(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onDragEnd");
- }
+ public void onDragEnd(float velocity) {
+ boolean fling = mDetector.isFling(velocity);
final int logAction = fling ? Touch.FLING : Touch.SWIPE;
boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
@@ -413,27 +405,24 @@
duration = 0;
startProgress = 1;
} else {
- startProgress = Utilities.boundToRange(
- progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
- duration = SwipeDetector.calculateDuration(velocity,
+ startProgress = Utilities.boundToRange(progress
+ + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
+ duration = BaseSwipeDetector.calculateDuration(velocity,
endProgress - Math.max(progress, 0)) * durationMultiplier;
}
} else {
// Let the state manager know that the animation didn't go to the target state,
// but don't cancel ourselves (we already clean up when the animation completes).
- Runnable onCancel = mCurrentAnimation.getOnCancelRunnable();
- mCurrentAnimation.setOnCancelRunnable(null);
- mCurrentAnimation.dispatchOnCancel();
- mCurrentAnimation.setOnCancelRunnable(onCancel);
+ mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable();
endProgress = 0;
if (progress <= 0) {
duration = 0;
startProgress = 0;
} else {
- startProgress = Utilities.boundToRange(
- progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
- duration = SwipeDetector.calculateDuration(velocity,
+ startProgress = Utilities.boundToRange(progress
+ + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
+ duration = BaseSwipeDetector.calculateDuration(velocity,
Math.min(progress, 1) - endProgress) * durationMultiplier;
}
}
@@ -515,9 +504,6 @@
}
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
- if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
- android.util.Log.e(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onSwipeInteractionCompleted 1");
- }
if (mAtomicComponentsController != null) {
mAtomicComponentsController.getAnimationPlayer().end();
mAtomicComponentsController = null;
@@ -531,18 +517,18 @@
shouldGoToTargetState = !reachedTarget;
}
if (shouldGoToTargetState) {
- if (targetState != mStartState) {
- logReachedState(logAction, targetState);
- }
- mLauncher.getStateManager().goToState(targetState, false /* animated */);
-
- if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
- android.util.Log.e(
- TestProtocol.NO_ALLAPPS_EVENT_TAG, "onSwipeInteractionCompleted 2");
- }
+ goToTargetState(targetState, logAction);
}
}
+ protected void goToTargetState(LauncherState targetState, int logAction) {
+ if (targetState != mStartState) {
+ logReachedState(logAction, targetState);
+ }
+ mLauncher.getStateManager().goToState(targetState, false /* animated */);
+ mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(1, 0, 0);
+ }
+
private void logReachedState(int logAction, LauncherState targetState) {
// Transition complete. log the action
mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
@@ -563,9 +549,6 @@
}
private void cancelAnimationControllers() {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "cancelAnimationControllers");
- }
mCurrentAnimation = null;
cancelAtomicComponentsController();
mDetector.finishedScrolling();
diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java
new file mode 100644
index 0000000..12ca5ee
--- /dev/null
+++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java
@@ -0,0 +1,268 @@
+/*
+ * 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.touch;
+
+import static android.view.MotionEvent.INVALID_POINTER_ID;
+
+import android.graphics.PointF;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Scroll/drag/swipe gesture detector.
+ *
+ * Definition of swipe is different from android system in that this detector handles
+ * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before
+ * swipe action happens.
+ *
+ * @see SingleAxisSwipeDetector
+ * @see BothAxesSwipeDetector
+ */
+public abstract class BaseSwipeDetector {
+
+ private static final boolean DBG = false;
+ private static final String TAG = "BaseSwipeDetector";
+ private static final float ANIMATION_DURATION = 1200;
+ /** The minimum release velocity in pixels per millisecond that triggers fling.*/
+ private static final float RELEASE_VELOCITY_PX_MS = 1.0f;
+ private static final PointF sTempPoint = new PointF();
+
+ private final PointF mDownPos = new PointF();
+ private final PointF mLastPos = new PointF();
+ protected final boolean mIsRtl;
+ protected final float mTouchSlop;
+ protected final float mMaxVelocity;
+
+ private int mActivePointerId = INVALID_POINTER_ID;
+ private VelocityTracker mVelocityTracker;
+ private PointF mLastDisplacement = new PointF();
+ private PointF mDisplacement = new PointF();
+ protected PointF mSubtractDisplacement = new PointF();
+ private ScrollState mState = ScrollState.IDLE;
+
+ protected boolean mIgnoreSlopWhenSettling;
+
+ private enum ScrollState {
+ IDLE,
+ DRAGGING, // onDragStart, onDrag
+ SETTLING // onDragEnd
+ }
+
+ protected BaseSwipeDetector(@NonNull ViewConfiguration config, boolean isRtl) {
+ mTouchSlop = config.getScaledTouchSlop();
+ mMaxVelocity = config.getScaledMaximumFlingVelocity();
+ mIsRtl = isRtl;
+ }
+
+ public static long calculateDuration(float velocity, float progressNeeded) {
+ // TODO: make these values constants after tuning.
+ float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
+ float travelDistance = Math.max(0.2f, progressNeeded);
+ long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
+ if (DBG) {
+ Log.d(TAG, String.format(
+ "calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
+ }
+ return duration;
+ }
+
+ public int getDownX() {
+ return (int) mDownPos.x;
+ }
+
+ public int getDownY() {
+ return (int) mDownPos.y;
+ }
+ /**
+ * There's no touch and there's no animation.
+ */
+ public boolean isIdleState() {
+ return mState == ScrollState.IDLE;
+ }
+
+ public boolean isSettlingState() {
+ return mState == ScrollState.SETTLING;
+ }
+
+ public boolean isDraggingState() {
+ return mState == ScrollState.DRAGGING;
+ }
+
+ public boolean isDraggingOrSettling() {
+ return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
+ }
+
+ public void finishedScrolling() {
+ setState(ScrollState.IDLE);
+ }
+
+ public boolean isFling(float velocity) {
+ return Math.abs(velocity) > RELEASE_VELOCITY_PX_MS;
+ }
+
+ public boolean onTouchEvent(MotionEvent ev) {
+ int actionMasked = ev.getActionMasked();
+ if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ switch (actionMasked) {
+ case MotionEvent.ACTION_DOWN:
+ mActivePointerId = ev.getPointerId(0);
+ mDownPos.set(ev.getX(), ev.getY());
+ mLastPos.set(mDownPos);
+ mLastDisplacement.set(0, 0);
+ mDisplacement.set(0, 0);
+
+ if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+ setState(ScrollState.DRAGGING);
+ }
+ break;
+ //case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ int ptrIdx = ev.getActionIndex();
+ int ptrId = ev.getPointerId(ptrIdx);
+ if (ptrId == mActivePointerId) {
+ final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+ mDownPos.set(
+ ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+ ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+ mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+ mActivePointerId = ev.getPointerId(newPointerIdx);
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == INVALID_POINTER_ID) {
+ break;
+ }
+ mDisplacement.set(ev.getX(pointerIndex) - mDownPos.x,
+ ev.getY(pointerIndex) - mDownPos.y);
+ if (mIsRtl) {
+ mDisplacement.x = -mDisplacement.x;
+ }
+
+ // handle state and listener calls.
+ if (mState != ScrollState.DRAGGING && shouldScrollStart(mDisplacement)) {
+ setState(ScrollState.DRAGGING);
+ }
+ if (mState == ScrollState.DRAGGING) {
+ reportDragging(ev);
+ }
+ mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ // These are synthetic events and there is no need to update internal values.
+ if (mState == ScrollState.DRAGGING) {
+ setState(ScrollState.SETTLING);
+ }
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ //------------------- ScrollState transition diagram -----------------------------------
+ //
+ // IDLE -> (mDisplacement > mTouchSlop) -> DRAGGING
+ // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
+ // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
+ // SETTLING -> (View settled) -> IDLE
+
+ private void setState(ScrollState newState) {
+ if (DBG) {
+ Log.d(TAG, "setState:" + mState + "->" + newState);
+ }
+ // onDragStart and onDragEnd is reported ONLY on state transition
+ if (newState == ScrollState.DRAGGING) {
+ initializeDragging();
+ if (mState == ScrollState.IDLE) {
+ reportDragStart(false /* recatch */);
+ } else if (mState == ScrollState.SETTLING) {
+ reportDragStart(true /* recatch */);
+ }
+ }
+ if (newState == ScrollState.SETTLING) {
+ reportDragEnd();
+ }
+
+ mState = newState;
+ }
+
+ private void initializeDragging() {
+ if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+ mSubtractDisplacement.set(0, 0);
+ } else {
+ mSubtractDisplacement.x = mDisplacement.x > 0 ? mTouchSlop : -mTouchSlop;
+ mSubtractDisplacement.y = mDisplacement.y > 0 ? mTouchSlop : -mTouchSlop;
+ }
+ }
+
+ protected abstract boolean shouldScrollStart(PointF displacement);
+
+ private void reportDragStart(boolean recatch) {
+ reportDragStartInternal(recatch);
+ if (DBG) {
+ Log.d(TAG, "onDragStart recatch:" + recatch);
+ }
+ }
+
+ protected abstract void reportDragStartInternal(boolean recatch);
+
+ private void reportDragging(MotionEvent event) {
+ if (mDisplacement != mLastDisplacement) {
+ if (DBG) {
+ Log.d(TAG, String.format("onDrag disp=%s", mDisplacement));
+ }
+
+ mLastDisplacement.set(mDisplacement);
+ sTempPoint.set(mDisplacement.x - mSubtractDisplacement.x,
+ mDisplacement.y - mSubtractDisplacement.y);
+ reportDraggingInternal(sTempPoint, event);
+ }
+ }
+
+ protected abstract void reportDraggingInternal(PointF displacement, MotionEvent event);
+
+ private void reportDragEnd() {
+ mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
+ PointF velocity = new PointF(mVelocityTracker.getXVelocity() / 1000,
+ mVelocityTracker.getYVelocity() / 1000);
+ if (mIsRtl) {
+ velocity.x = -velocity.x;
+ }
+ if (DBG) {
+ Log.d(TAG, String.format("onScrollEnd disp=%.1s, velocity=%.1s",
+ mDisplacement, velocity));
+ }
+
+ reportDragEndInternal(velocity);
+ }
+
+ protected abstract void reportDragEndInternal(PointF velocity);
+}
diff --git a/src/com/android/launcher3/touch/BothAxesSwipeDetector.java b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
new file mode 100644
index 0000000..944391e
--- /dev/null
+++ b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2019 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.touch;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * Two dimensional scroll/drag/swipe gesture detector that reports x and y displacement/velocity.
+ */
+public class BothAxesSwipeDetector extends BaseSwipeDetector {
+
+ public static final int DIRECTION_UP = 1 << 0;
+ // Note that this will track left instead of right in RTL.
+ public static final int DIRECTION_RIGHT = 1 << 1;
+ public static final int DIRECTION_DOWN = 1 << 2;
+ // Note that this will track right instead of left in RTL.
+ public static final int DIRECTION_LEFT = 1 << 3;
+
+ /* Client of this gesture detector can register a callback. */
+ private final Listener mListener;
+
+ private int mScrollDirections;
+
+ public BothAxesSwipeDetector(@NonNull Context context, @NonNull Listener l) {
+ this(ViewConfiguration.get(context), l, Utilities.isRtl(context.getResources()));
+ }
+
+ @VisibleForTesting
+ protected BothAxesSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
+ boolean isRtl) {
+ super(config, isRtl);
+ mListener = l;
+ }
+
+ public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
+ mScrollDirections = scrollDirectionFlags;
+ mIgnoreSlopWhenSettling = ignoreSlop;
+ }
+
+ @Override
+ protected boolean shouldScrollStart(PointF displacement) {
+ // Check if the client is interested in scroll in current direction.
+ boolean canScrollUp = (mScrollDirections & DIRECTION_UP) > 0
+ && displacement.y <= -mTouchSlop;
+ boolean canScrollRight = (mScrollDirections & DIRECTION_RIGHT) > 0
+ && displacement.x >= mTouchSlop;
+ boolean canScrollDown = (mScrollDirections & DIRECTION_DOWN) > 0
+ && displacement.y >= mTouchSlop;
+ boolean canScrollLeft = (mScrollDirections & DIRECTION_LEFT) > 0
+ && displacement.x <= -mTouchSlop;
+ return canScrollUp || canScrollRight || canScrollDown || canScrollLeft;
+ }
+
+ @Override
+ protected void reportDragStartInternal(boolean recatch) {
+ mListener.onDragStart(!recatch);
+ }
+
+ @Override
+ protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
+ mListener.onDrag(displacement, event);
+ }
+
+ @Override
+ protected void reportDragEndInternal(PointF velocity) {
+ mListener.onDragEnd(velocity);
+ }
+
+ /** Listener to receive updates on the swipe. */
+ public interface Listener {
+ /** @param start whether this was the original drag start, as opposed to a recatch. */
+ void onDragStart(boolean start);
+
+ boolean onDrag(PointF displacement, MotionEvent motionEvent);
+
+ void onDragEnd(PointF velocity);
+ }
+}
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 85f763d..03493a5 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -25,9 +25,15 @@
import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS;
import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageInstaller.SessionInfo;
import android.os.Process;
+import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;
@@ -43,11 +49,12 @@
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.PromiseAppInfo;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.widget.PendingAppWidgetHostView;
@@ -58,6 +65,8 @@
*/
public class ItemClickHandler {
+ private static final String TAG = ItemClickHandler.class.getSimpleName();
+
/**
* Instance used for click handling on items
*/
@@ -68,28 +77,12 @@
}
private static void onClick(View v, String sourceContainer) {
- if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_START_TAG,
- "onClick 1");
- }
// Make sure that rogue clicks don't get through while allapps is launching, or after the
// view has detached (it's possible for this to happen if the view is removed mid touch).
- if (v.getWindowToken() == null) {
- if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_START_TAG,
- "onClick 2");
- }
- return;
- }
+ if (v.getWindowToken() == null) return;
Launcher launcher = Launcher.getLauncher(v.getContext());
- if (!launcher.getWorkspace().isFinishedSwitchingState()) {
- if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_START_TAG,
- "onClick 3");
- }
- return;
- }
+ if (!launcher.getWorkspace().isFinishedSwitchingState()) return;
Object tag = v.getTag();
if (tag instanceof WorkspaceItemInfo) {
@@ -99,10 +92,6 @@
onClickFolderIcon(v);
}
} else if (tag instanceof AppInfo) {
- if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_START_TAG,
- "onClick 4");
- }
startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher,
sourceContainer == null ? CONTAINER_ALL_APPS: sourceContainer);
} else if (tag instanceof LauncherAppWidgetInfo) {
@@ -166,6 +155,8 @@
startMarketIntentForPackage(v, launcher, packageName);
return;
}
+ UserHandle user = v.getTag() instanceof ItemInfo
+ ? ((ItemInfo) v.getTag()).user : Process.myUserHandle();
new AlertDialog.Builder(launcher)
.setTitle(R.string.abandoned_promises_title)
.setMessage(R.string.abandoned_promise_explanation)
@@ -173,12 +164,28 @@
(d, i) -> startMarketIntentForPackage(v, launcher, packageName))
.setNeutralButton(R.string.abandoned_clean_this,
(d, i) -> launcher.getWorkspace()
- .removeAbandonedPromise(packageName, Process.myUserHandle()))
+ .removeAbandonedPromise(packageName, user))
.create().show();
}
private static void startMarketIntentForPackage(View v, Launcher launcher, String packageName) {
ItemInfo item = (ItemInfo) v.getTag();
+ if (Utilities.ATLEAST_Q) {
+ PackageInstallerCompat pkgInstaller = PackageInstallerCompat.getInstance(launcher);
+ SessionInfo sessionInfo = pkgInstaller.getActiveSessionInfo(item.user, packageName);
+ if (sessionInfo != null) {
+ LauncherApps launcherApps = launcher.getSystemService(LauncherApps.class);
+ try {
+ launcherApps.startPackageInstallerSessionDetailsActivity(sessionInfo, null,
+ launcher.getActivityLaunchOptionsAsBundle(v));
+ return;
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to launch market intent for package=" + packageName, e);
+ }
+ }
+ }
+
+ // Fallback to using custom market intent.
Intent intent = new PackageManagerHelper(launcher).getMarketIntent(packageName);
launcher.startActivitySafely(v, intent, item, null);
}
@@ -234,10 +241,6 @@
private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher,
@Nullable String sourceContainer) {
- if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_START_TAG,
- "startAppShortcutOrInfoActivity");
- }
Intent intent;
if (item instanceof PromiseAppInfo) {
PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index babbcdd..86d2b39 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -17,10 +17,12 @@
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
+
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
+import android.util.Log;
import android.view.View;
import android.view.View.OnLongClickListener;
@@ -32,6 +34,9 @@
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
+import com.android.launcher3.testing.TestProtocol;
+
+import java.util.Arrays;
/**
* Class to handle long-clicks on workspace items and start drag as a result.
@@ -60,7 +65,7 @@
if (info.container >= 0) {
Folder folder = Folder.getOpen(launcher);
if (folder != null) {
- if (!folder.getItemsInReadingOrder().contains(v)) {
+ if (!folder.getIconsInReadingOrder().contains(v)) {
folder.close(true);
} else {
folder.startDrag(v, dragOptions);
@@ -74,10 +79,19 @@
}
private static boolean onAllAppsItemLongClick(View v) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick1");
+ }
Launcher launcher = Launcher.getLauncher(v.getContext());
if (!canStartDrag(launcher)) return false;
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick2");
+ }
// When we have exited all apps or are in transition, disregard long clicks
if (!launcher.isInState(ALL_APPS) && !launcher.isInState(OVERVIEW)) return false;
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick3");
+ }
if (launcher.getWorkspace().isSwitchingState()) return false;
// Start the drag
diff --git a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
new file mode 100644
index 0000000..f2ebc45
--- /dev/null
+++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2019 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.touch;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * One dimensional scroll/drag/swipe gesture detector (either HORIZONTAL or VERTICAL).
+ */
+public class SingleAxisSwipeDetector extends BaseSwipeDetector {
+
+ public static final int DIRECTION_POSITIVE = 1 << 0;
+ public static final int DIRECTION_NEGATIVE = 1 << 1;
+ public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE;
+
+ public static final Direction VERTICAL = new Direction() {
+
+ @Override
+ boolean isPositive(float displacement) {
+ // Up
+ return displacement < 0;
+ }
+
+ @Override
+ boolean isNegative(float displacement) {
+ // Down
+ return displacement > 0;
+ }
+
+ @Override
+ float extractDirection(PointF direction) {
+ return direction.y;
+ }
+
+ @Override
+ boolean canScrollStart(PointF displacement, float touchSlop) {
+ return Math.abs(displacement.y) >= Math.max(Math.abs(displacement.x), touchSlop);
+ }
+
+ };
+
+ public static final Direction HORIZONTAL = new Direction() {
+
+ @Override
+ boolean isPositive(float displacement) {
+ // Right
+ return displacement > 0;
+ }
+
+ @Override
+ boolean isNegative(float displacement) {
+ // Left
+ return displacement < 0;
+ }
+
+ @Override
+ float extractDirection(PointF direction) {
+ return direction.x;
+ }
+
+ @Override
+ boolean canScrollStart(PointF displacement, float touchSlop) {
+ return Math.abs(displacement.x) >= Math.max(Math.abs(displacement.y), touchSlop);
+ }
+ };
+
+ private final Direction mDir;
+ /* Client of this gesture detector can register a callback. */
+ private final Listener mListener;
+
+ private int mScrollDirections;
+
+ public SingleAxisSwipeDetector(@NonNull Context context, @NonNull Listener l,
+ @NonNull Direction dir) {
+ this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources()));
+ }
+
+ @VisibleForTesting
+ protected SingleAxisSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
+ @NonNull Direction dir, boolean isRtl) {
+ super(config, isRtl);
+ mListener = l;
+ mDir = dir;
+ }
+
+ public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
+ mScrollDirections = scrollDirectionFlags;
+ mIgnoreSlopWhenSettling = ignoreSlop;
+ }
+
+ public int getScrollDirections() {
+ return mScrollDirections;
+ }
+
+ /**
+ * Returns if the start drag was towards the positive direction or negative.
+ *
+ * @see #setDetectableScrollConditions(int, boolean)
+ * @see #DIRECTION_BOTH
+ */
+ public boolean wasInitialTouchPositive() {
+ return mDir.isPositive(mDir.extractDirection(mSubtractDisplacement));
+ }
+
+ @Override
+ protected boolean shouldScrollStart(PointF displacement) {
+ // Reject cases where the angle or slop condition is not met.
+ if (!mDir.canScrollStart(displacement, mTouchSlop)) {
+ return false;
+ }
+
+ // Check if the client is interested in scroll in current direction.
+ float displacementComponent = mDir.extractDirection(displacement);
+ return canScrollNegative(displacementComponent) || canScrollPositive(displacementComponent);
+ }
+
+ private boolean canScrollNegative(float displacement) {
+ return (mScrollDirections & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(displacement);
+ }
+
+ private boolean canScrollPositive(float displacement) {
+ return (mScrollDirections & DIRECTION_POSITIVE) > 0 && mDir.isPositive(displacement);
+ }
+
+ @Override
+ protected void reportDragStartInternal(boolean recatch) {
+ mListener.onDragStart(!recatch);
+ }
+
+ @Override
+ protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
+ mListener.onDrag(mDir.extractDirection(displacement), event);
+ }
+
+ @Override
+ protected void reportDragEndInternal(PointF velocity) {
+ float velocityComponent = mDir.extractDirection(velocity);
+ mListener.onDragEnd(velocityComponent);
+ }
+
+ /** Listener to receive updates on the swipe. */
+ public interface Listener {
+ /** @param start whether this was the original drag start, as opposed to a recatch. */
+ void onDragStart(boolean start);
+
+ // TODO remove
+ boolean onDrag(float displacement);
+
+ default boolean onDrag(float displacement, MotionEvent event) {
+ return onDrag(displacement);
+ }
+
+ void onDragEnd(float velocity);
+ }
+
+ public abstract static class Direction {
+
+ abstract boolean isPositive(float displacement);
+
+ abstract boolean isNegative(float displacement);
+
+ /** Returns the part of the given {@link PointF} that is relevant to this direction. */
+ abstract float extractDirection(PointF point);
+
+ /** Reject cases where the angle or slop condition is not met. */
+ abstract boolean canScrollStart(PointF displacement, float touchSlop);
+
+ }
+}
diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java
deleted file mode 100644
index 3d45404..0000000
--- a/src/com/android/launcher3/touch/SwipeDetector.java
+++ /dev/null
@@ -1,424 +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.touch;
-
-import static android.view.MotionEvent.INVALID_POINTER_ID;
-
-import android.content.Context;
-import android.graphics.PointF;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.ViewConfiguration;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-/**
- * One dimensional scroll/drag/swipe gesture detector.
- *
- * Definition of swipe is different from android system in that this detector handles
- * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before
- * swipe action happens
- */
-public class SwipeDetector {
-
- private static final boolean DBG = false;
- private static final String TAG = "SwipeDetector";
-
- private int mScrollConditions;
- public static final int DIRECTION_POSITIVE = 1 << 0;
- public static final int DIRECTION_NEGATIVE = 1 << 1;
- public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE;
-
- private static final float ANIMATION_DURATION = 1200;
-
- protected int mActivePointerId = INVALID_POINTER_ID;
-
- /**
- * The minimum release velocity in pixels per millisecond that triggers fling..
- */
- public static final float RELEASE_VELOCITY_PX_MS = 1.0f;
-
- /* Scroll state, this is set to true during dragging and animation. */
- private ScrollState mState = ScrollState.IDLE;
-
- enum ScrollState {
- IDLE,
- DRAGGING, // onDragStart, onDrag
- SETTLING // onDragEnd
- }
-
- public static abstract class Direction {
-
- abstract float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint,
- boolean isRtl);
-
- /**
- * Distance in pixels a touch can wander before we think the user is scrolling.
- */
- abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos);
-
- abstract float getVelocity(VelocityTracker tracker, boolean isRtl);
-
- abstract boolean isPositive(float displacement);
-
- abstract boolean isNegative(float displacement);
- }
-
- public static final Direction VERTICAL = new Direction() {
-
- @Override
- float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint, boolean isRtl) {
- return ev.getY(pointerIndex) - refPoint.y;
- }
-
- @Override
- float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
- return Math.abs(ev.getX(pointerIndex) - downPos.x);
- }
-
- @Override
- float getVelocity(VelocityTracker tracker, boolean isRtl) {
- return tracker.getYVelocity();
- }
-
- @Override
- boolean isPositive(float displacement) {
- // Up
- return displacement < 0;
- }
-
- @Override
- boolean isNegative(float displacement) {
- // Down
- return displacement > 0;
- }
- };
-
- public static final Direction HORIZONTAL = new Direction() {
-
- @Override
- float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint, boolean isRtl) {
- float displacement = ev.getX(pointerIndex) - refPoint.x;
- if (isRtl) {
- displacement = -displacement;
- }
- return displacement;
- }
-
- @Override
- float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
- return Math.abs(ev.getY(pointerIndex) - downPos.y);
- }
-
- @Override
- float getVelocity(VelocityTracker tracker, boolean isRtl) {
- float velocity = tracker.getXVelocity();
- if (isRtl) {
- velocity = -velocity;
- }
- return velocity;
- }
-
- @Override
- boolean isPositive(float displacement) {
- // Right
- return displacement > 0;
- }
-
- @Override
- boolean isNegative(float displacement) {
- // Left
- return displacement < 0;
- }
- };
-
- //------------------- ScrollState transition diagram -----------------------------------
- //
- // IDLE -> (mDisplacement > mTouchSlop) -> DRAGGING
- // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
- // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
- // SETTLING -> (View settled) -> IDLE
-
- private void setState(ScrollState newState) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "setState -- start: " + newState);
- }
- if (DBG) {
- Log.d(TAG, "setState:" + mState + "->" + newState);
- }
- // onDragStart and onDragEnd is reported ONLY on state transition
- if (newState == ScrollState.DRAGGING) {
- initializeDragging();
- if (mState == ScrollState.IDLE) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "setState -- 1: " + newState);
- }
- reportDragStart(false /* recatch */);
- } else if (mState == ScrollState.SETTLING) {
- reportDragStart(true /* recatch */);
- }
- }
- if (newState == ScrollState.SETTLING) {
- reportDragEnd();
- }
-
- mState = newState;
- if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
- android.util.Log.e(TestProtocol.NO_ALLAPPS_EVENT_TAG,
- "setState: " + newState + " @ " + android.util.Log.getStackTraceString(
- new Throwable()));
- }
- }
-
- public boolean isDraggingOrSettling() {
- return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
- }
-
- public int getDownX() {
- return (int) mDownPos.x;
- }
-
- public int getDownY() {
- return (int) mDownPos.y;
- }
- /**
- * There's no touch and there's no animation.
- */
- public boolean isIdleState() {
- return mState == ScrollState.IDLE;
- }
-
- public boolean isSettlingState() {
- return mState == ScrollState.SETTLING;
- }
-
- public boolean isDraggingState() {
- return mState == ScrollState.DRAGGING;
- }
-
- private final PointF mDownPos = new PointF();
- private final PointF mLastPos = new PointF();
- private final Direction mDir;
- private final boolean mIsRtl;
-
- private final float mTouchSlop;
- private final float mMaxVelocity;
-
- /* Client of this gesture detector can register a callback. */
- private final Listener mListener;
-
- private VelocityTracker mVelocityTracker;
-
- private float mLastDisplacement;
- private float mDisplacement;
-
- private float mSubtractDisplacement;
- private boolean mIgnoreSlopWhenSettling;
-
- public interface Listener {
- void onDragStart(boolean start);
-
- boolean onDrag(float displacement);
-
- default boolean onDrag(float displacement, MotionEvent event) {
- return onDrag(displacement);
- }
-
- void onDragEnd(float velocity, boolean fling);
- }
-
- public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) {
- this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources()));
- }
-
- @VisibleForTesting
- protected SwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
- @NonNull Direction dir, boolean isRtl) {
- mListener = l;
- mDir = dir;
- mIsRtl = isRtl;
- mTouchSlop = config.getScaledTouchSlop();
- mMaxVelocity = config.getScaledMaximumFlingVelocity();
- }
-
- public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
- mScrollConditions = scrollDirectionFlags;
- mIgnoreSlopWhenSettling = ignoreSlop;
- }
-
- public int getScrollDirections() {
- return mScrollConditions;
- }
-
- private boolean shouldScrollStart(MotionEvent ev, int pointerIndex) {
- // reject cases where the angle or slop condition is not met.
- if (Math.max(mDir.getActiveTouchSlop(ev, pointerIndex, mDownPos), mTouchSlop)
- > Math.abs(mDisplacement)) {
- return false;
- }
-
- // Check if the client is interested in scroll in current direction.
- if (((mScrollConditions & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(mDisplacement)) ||
- ((mScrollConditions & DIRECTION_POSITIVE) > 0 && mDir.isPositive(mDisplacement))) {
- return true;
- }
- return false;
- }
-
- public boolean onTouchEvent(MotionEvent ev) {
- int actionMasked = ev.getActionMasked();
- if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) {
- mVelocityTracker.clear();
- }
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(ev);
-
- switch (actionMasked) {
- case MotionEvent.ACTION_DOWN:
- mActivePointerId = ev.getPointerId(0);
- mDownPos.set(ev.getX(), ev.getY());
- mLastPos.set(mDownPos);
- mLastDisplacement = 0;
- mDisplacement = 0;
-
- if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
- setState(ScrollState.DRAGGING);
- }
- break;
- //case MotionEvent.ACTION_POINTER_DOWN:
- case MotionEvent.ACTION_POINTER_UP:
- int ptrIdx = ev.getActionIndex();
- int ptrId = ev.getPointerId(ptrIdx);
- if (ptrId == mActivePointerId) {
- final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
- mDownPos.set(
- ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
- ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
- mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
- mActivePointerId = ev.getPointerId(newPointerIdx);
- }
- break;
- case MotionEvent.ACTION_MOVE:
- int pointerIndex = ev.findPointerIndex(mActivePointerId);
- if (pointerIndex == INVALID_POINTER_ID) {
- break;
- }
- mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos, mIsRtl);
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onTouchEvent 1");
- }
-
- // handle state and listener calls.
- if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onTouchEvent 2");
- }
- setState(ScrollState.DRAGGING);
- }
- if (mState == ScrollState.DRAGGING) {
- reportDragging(ev);
- }
- mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
- break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- // These are synthetic events and there is no need to update internal values.
- if (mState == ScrollState.DRAGGING) {
- setState(ScrollState.SETTLING);
- }
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- break;
- default:
- break;
- }
- return true;
- }
-
- public void finishedScrolling() {
- setState(ScrollState.IDLE);
- }
-
- private boolean reportDragStart(boolean recatch) {
- mListener.onDragStart(!recatch);
- if (DBG) {
- Log.d(TAG, "onDragStart recatch:" + recatch);
- }
- return true;
- }
-
- private void initializeDragging() {
- if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
- mSubtractDisplacement = 0;
- }
- if (mDisplacement > 0) {
- mSubtractDisplacement = mTouchSlop;
- } else {
- mSubtractDisplacement = -mTouchSlop;
- }
- }
-
- /**
- * Returns if the start drag was towards the positive direction or negative.
- *
- * @see #setDetectableScrollConditions(int, boolean)
- * @see #DIRECTION_BOTH
- */
- public boolean wasInitialTouchPositive() {
- return mDir.isPositive(mSubtractDisplacement);
- }
-
- private boolean reportDragging(MotionEvent event) {
- if (mDisplacement != mLastDisplacement) {
- if (DBG) {
- Log.d(TAG, String.format("onDrag disp=%.1f", mDisplacement));
- }
-
- mLastDisplacement = mDisplacement;
- return mListener.onDrag(mDisplacement - mSubtractDisplacement, event);
- }
- return true;
- }
-
- private void reportDragEnd() {
- mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
- float velocity = mDir.getVelocity(mVelocityTracker, mIsRtl) / 1000;
- if (DBG) {
- Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f",
- mDisplacement, velocity));
- }
-
- mListener.onDragEnd(velocity, Math.abs(velocity) > RELEASE_VELOCITY_PX_MS);
- }
-
- public static long calculateDuration(float velocity, float progressNeeded) {
- // TODO: make these values constants after tuning.
- float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
- float travelDistance = Math.max(0.2f, progressNeeded);
- long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
- if (DBG) {
- Log.d(TAG, String.format("calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
- }
- return duration;
- }
-}
diff --git a/src/com/android/launcher3/touch/TouchEventTranslator.java b/src/com/android/launcher3/touch/TouchEventTranslator.java
deleted file mode 100644
index 3fcda90..0000000
--- a/src/com/android/launcher3/touch/TouchEventTranslator.java
+++ /dev/null
@@ -1,283 +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.touch;
-
-import android.graphics.PointF;
-import android.util.Log;
-import android.util.Pair;
-import android.util.SparseArray;
-import android.view.MotionEvent;
-import android.view.MotionEvent.PointerCoords;
-import android.view.MotionEvent.PointerProperties;
-
-import java.util.function.Consumer;
-
-/**
- * To minimize the size of the MotionEvent, historic events are not copied and passed via the
- * listener.
- */
-public class TouchEventTranslator {
-
- private static final String TAG = "TouchEventTranslator";
- private static final boolean DEBUG = false;
-
- private class DownState {
- long timeStamp;
- float downX;
- float downY;
- public DownState(long timeStamp, float downX, float downY) {
- this.timeStamp = timeStamp;
- this.downX = downX;
- this.downY = downY;
- }
- };
- private final DownState ZERO = new DownState(0, 0f, 0f);
-
- private final Consumer<MotionEvent> mListener;
-
- private final SparseArray<DownState> mDownEvents;
- private final SparseArray<PointF> mFingers;
-
- private final SparseArray<Pair<PointerProperties[], PointerCoords[]>> mCache;
-
- public TouchEventTranslator(Consumer<MotionEvent> listener) {
- mDownEvents = new SparseArray<>();
- mFingers = new SparseArray<>();
- mCache = new SparseArray<>();
-
- mListener = listener;
- }
-
- public void reset() {
- mDownEvents.clear();
- mFingers.clear();
- }
-
- public float getDownX() {
- return mDownEvents.get(0).downX;
- }
-
- public float getDownY() {
- return mDownEvents.get(0).downY;
- }
-
- public void setDownParameters(int idx, MotionEvent e) {
- DownState ev = new DownState(e.getEventTime(), e.getX(idx), e.getY(idx));
- mDownEvents.append(idx, ev);
- }
-
- public void dispatchDownEvents(MotionEvent ev) {
- for(int i = 0; i < ev.getPointerCount() && i < mDownEvents.size(); i++) {
- int pid = ev.getPointerId(i);
- put(pid, i, ev.getX(i), 0, mDownEvents.get(i).timeStamp, ev);
- }
- }
-
- public void processMotionEvent(MotionEvent ev) {
- if (DEBUG) {
- printSamples(TAG + " processMotionEvent", ev);
- }
- int index = ev.getActionIndex();
- float x = ev.getX(index);
- float y = ev.getY(index) - mDownEvents.get(index, ZERO).downY;
- switch (ev.getActionMasked()) {
- case MotionEvent.ACTION_POINTER_DOWN:
- int pid = ev.getPointerId(index);
- if(mFingers.get(pid, null) != null) {
- for(int i=0; i < ev.getPointerCount(); i++) {
- pid = ev.getPointerId(i);
- position(pid, x, y);
- }
- generateEvent(ev.getAction(), ev);
- } else {
- put(pid, index, x, y, ev);
- }
- break;
- case MotionEvent.ACTION_MOVE:
- for(int i=0; i < ev.getPointerCount(); i++) {
- pid = ev.getPointerId(i);
- position(pid, x, y);
- }
- generateEvent(ev.getAction(), ev);
- break;
- case MotionEvent.ACTION_POINTER_UP:
- case MotionEvent.ACTION_UP:
- pid = ev.getPointerId(index);
- lift(pid, index, x, y, ev);
- break;
- case MotionEvent.ACTION_CANCEL:
- cancel(ev);
- break;
- default:
- Log.v(TAG, "Didn't process ");
- printSamples(TAG, ev);
-
- }
- }
-
- private TouchEventTranslator put(int id, int index, float x, float y, MotionEvent ev) {
- return put(id, index, x, y, ev.getEventTime(), ev);
- }
-
- private TouchEventTranslator put(int id, int index, float x, float y, long ms, MotionEvent ev) {
- checkFingerExistence(id, false);
- boolean isInitialDown = (mFingers.size() == 0);
-
- mFingers.put(id, new PointF(x, y));
- int n = mFingers.size();
-
- if (mCache.get(n) == null) {
- PointerProperties[] properties = new PointerProperties[n];
- PointerCoords[] coords = new PointerCoords[n];
- for (int i = 0; i < n; i++) {
- properties[i] = new PointerProperties();
- coords[i] = new PointerCoords();
- }
- mCache.put(n, new Pair(properties, coords));
- }
-
- int action;
- if (isInitialDown) {
- action = MotionEvent.ACTION_DOWN;
- } else {
- action = MotionEvent.ACTION_POINTER_DOWN;
- // Set the id of the changed pointer.
- action |= index << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
- }
- generateEvent(action, ms, ev);
- return this;
- }
-
- public TouchEventTranslator position(int id, float x, float y) {
- checkFingerExistence(id, true);
- mFingers.get(id).set(x, y);
- return this;
- }
-
- private TouchEventTranslator lift(int id, int index, MotionEvent ev) {
- checkFingerExistence(id, true);
- boolean isFinalUp = (mFingers.size() == 1);
- int action;
- if (isFinalUp) {
- action = MotionEvent.ACTION_UP;
- } else {
- action = MotionEvent.ACTION_POINTER_UP;
- // Set the id of the changed pointer.
- action |= index << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
- }
- generateEvent(action, ev);
- mFingers.remove(id);
- return this;
- }
-
- private TouchEventTranslator lift(int id, int index, float x, float y, MotionEvent ev) {
- checkFingerExistence(id, true);
- mFingers.get(id).set(x, y);
- return lift(id, index, ev);
- }
-
- public TouchEventTranslator cancel(MotionEvent ev) {
- generateEvent(MotionEvent.ACTION_CANCEL, ev);
- mFingers.clear();
- return this;
- }
-
- private void checkFingerExistence(int id, boolean shouldExist) {
- if (shouldExist != (mFingers.get(id, null) != null)) {
- throw new IllegalArgumentException(
- shouldExist ? "Finger does not exist" : "Finger already exists");
- }
- }
-
-
- /**
- * Used to debug MotionEvents being sent/received.
- */
- public void printSamples(String msg, MotionEvent ev) {
- System.out.printf("%s %s", msg, MotionEvent.actionToString(ev.getActionMasked()));
- final int pointerCount = ev.getPointerCount();
- System.out.printf("#%d/%d", ev.getActionIndex(), pointerCount);
- System.out.printf(" t=%d:", ev.getEventTime());
- for (int p = 0; p < pointerCount; p++) {
- System.out.printf(" id=%d: (%f,%f)",
- ev.getPointerId(p), ev.getX(p), ev.getY(p));
- }
- System.out.println();
- }
-
- private void generateEvent(int action, MotionEvent ev) {
- generateEvent(action, ev.getEventTime(), ev);
- }
-
- private void generateEvent(int action, long ms, MotionEvent ev) {
- Pair<PointerProperties[], PointerCoords[]> state = getFingerState();
- MotionEvent event = MotionEvent.obtain(
- mDownEvents.get(0).timeStamp,
- ms,
- action,
- state.first.length,
- state.first,
- state.second,
- ev.getMetaState(),
- ev.getButtonState() /* buttonState */,
- ev.getXPrecision() /* xPrecision */,
- ev.getYPrecision() /* yPrecision */,
- ev.getDeviceId(),
- ev.getEdgeFlags(),
- ev.getSource(),
- ev.getFlags() /* flags */);
- if (DEBUG) {
- printSamples(TAG + " generateEvent", event);
- }
- if (event.getPointerId(event.getActionIndex()) < 0) {
- printSamples(TAG + "generateEvent", event);
- throw new IllegalStateException(event.getActionIndex() + " not found in MotionEvent");
- }
- mListener.accept(event);
- event.recycle();
- }
-
- /**
- * Returns the description of the fingers' state expected by MotionEvent.
- */
- private Pair<PointerProperties[], PointerCoords[]> getFingerState() {
- int nFingers = mFingers.size();
-
- Pair<PointerProperties[], PointerCoords[]> result = mCache.get(nFingers);
- PointerProperties[] properties = result.first;
- PointerCoords[] coordinates = result.second;
-
- int index = 0;
- for (int i = 0; i < mFingers.size(); i++) {
- int id = mFingers.keyAt(i);
- PointF location = mFingers.get(id);
-
- PointerProperties property = properties[i];
- property.id = id;
- property.toolType = MotionEvent.TOOL_TYPE_FINGER;
- properties[index] = property;
-
- PointerCoords coordinate = coordinates[i];
- coordinate.x = location.x;
- coordinate.y = location.y;
- coordinate.pressure = 1.0f;
- coordinates[index] = coordinate;
-
- index++;
- }
- return mCache.get(nFingers);
- }
-}
diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java
index 12d35e9..0f81520 100644
--- a/src/com/android/launcher3/util/ConfigMonitor.java
+++ b/src/com/android/launcher3/util/ConfigMonitor.java
@@ -16,20 +16,15 @@
* limitations under the License.
*/
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.graphics.Point;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
-import android.os.Handler;
import android.util.Log;
-import android.view.Display;
-import android.view.WindowManager;
-
-import com.android.launcher3.MainThreadExecutor;
import java.util.function.Consumer;
@@ -37,7 +32,8 @@
* {@link BroadcastReceiver} which watches configuration changes and
* notifies the callback in case changes which affect the device profile occur.
*/
-public class ConfigMonitor extends BroadcastReceiver implements DisplayListener {
+public class ConfigMonitor extends BroadcastReceiver implements
+ DefaultDisplay.DisplayInfoChangeListener {
private static final String TAG = "ConfigMonitor";
@@ -61,24 +57,19 @@
mFontScale = config.fontScale;
mDensity = config.densityDpi;
- Display display = getDefaultDisplay(context);
- mDisplayId = display.getDisplayId();
+ DefaultDisplay display = DefaultDisplay.INSTANCE.get(context);
+ display.addChangeListener(this);
+ DefaultDisplay.Info displayInfo = display.getInfo();
+ mDisplayId = displayInfo.id;
- mRealSize = new Point();
- display.getRealSize(mRealSize);
-
- mSmallestSize = new Point();
- mLargestSize = new Point();
- display.getCurrentSizeRange(mSmallestSize, mLargestSize);
+ mRealSize = new Point(displayInfo.realSize);
+ mSmallestSize = new Point(displayInfo.smallestSize);
+ mLargestSize = new Point(displayInfo.largestSize);
mCallback = callback;
// Listen for configuration change
mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
-
- // Listen for display manager change
- mContext.getSystemService(DisplayManager.class)
- .registerDisplayListener(this, new Handler(UiThreadHelper.getBackgroundLooper()));
}
@Override
@@ -91,26 +82,19 @@
}
@Override
- public void onDisplayAdded(int displayId) { }
-
- @Override
- public void onDisplayRemoved(int displayId) { }
-
- @Override
- public void onDisplayChanged(int displayId) {
- if (displayId != mDisplayId) {
+ public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
+ if (info.id != mDisplayId) {
return;
}
- Display display = getDefaultDisplay(mContext);
- display.getRealSize(mTmpPoint1);
-
+ mTmpPoint1.set(info.realSize.x, info.realSize.y);
if (!mRealSize.equals(mTmpPoint1) && !mRealSize.equals(mTmpPoint1.y, mTmpPoint1.x)) {
Log.d(TAG, String.format("Display size changed from %s to %s", mRealSize, mTmpPoint1));
notifyChange();
return;
}
- display.getCurrentSizeRange(mTmpPoint1, mTmpPoint2);
+ mTmpPoint1.set(info.smallestSize.x, info.smallestSize.y);
+ mTmpPoint2.set(info.largestSize.x, info.largestSize.y);
if (!mSmallestSize.equals(mTmpPoint1) || !mLargestSize.equals(mTmpPoint2)) {
Log.d(TAG, String.format("Available size changed from [%s, %s] to [%s, %s]",
mSmallestSize, mLargestSize, mTmpPoint1, mTmpPoint2));
@@ -122,18 +106,15 @@
if (mCallback != null) {
Consumer<Context> callback = mCallback;
mCallback = null;
- new MainThreadExecutor().execute(() -> callback.accept(mContext));
+ MAIN_EXECUTOR.execute(() -> callback.accept(mContext));
}
}
- private Display getDefaultDisplay(Context context) {
- return context.getSystemService(WindowManager.class).getDefaultDisplay();
- }
-
public void unregister() {
try {
mContext.unregisterReceiver(this);
- mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+ DefaultDisplay display = DefaultDisplay.INSTANCE.get(mContext);
+ display.removeChangeListener(this);
} catch (Exception e) {
Log.e(TAG, "Failed to unregister config monitor", e);
}
diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java
new file mode 100644
index 0000000..8529d50
--- /dev/null
+++ b/src/com/android/launcher3/util/DefaultDisplay.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2019 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 static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Handler;
+import android.os.Message;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to cache properties of default display to avoid a system RPC on every call.
+ */
+public class DefaultDisplay implements DisplayListener {
+
+ public static final MainThreadInitializedObject<DefaultDisplay> INSTANCE =
+ new MainThreadInitializedObject<>(DefaultDisplay::new);
+
+ private static final String TAG = "DefaultDisplay";
+
+ public static final int CHANGE_SIZE = 1 << 0;
+ public static final int CHANGE_ROTATION = 1 << 1;
+ public static final int CHANGE_FRAME_DELAY = 1 << 2;
+
+ private final Context mContext;
+ private final int mId;
+ private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
+ private final Handler mChangeHandler;
+ private Info mInfo;
+
+ private DefaultDisplay(Context context) {
+ mContext = context;
+ mInfo = new Info(context);
+ mId = mInfo.id;
+ mChangeHandler = new Handler(this::onChange);
+
+ context.getSystemService(DisplayManager.class)
+ .registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
+ }
+
+ @Override
+ public final void onDisplayAdded(int displayId) { }
+
+ @Override
+ public final void onDisplayRemoved(int displayId) { }
+
+ @Override
+ public final void onDisplayChanged(int displayId) {
+ if (displayId != mId) {
+ return;
+ }
+
+ Info oldInfo = mInfo;
+ Info info = new Info(mContext);
+
+ int change = 0;
+ if (info.hasDifferentSize(oldInfo)) {
+ change |= CHANGE_SIZE;
+ }
+ if (oldInfo.rotation != info.rotation) {
+ change |= CHANGE_ROTATION;
+ }
+ if (info.singleFrameMs != oldInfo.singleFrameMs) {
+ change |= CHANGE_FRAME_DELAY;
+ }
+
+ if (change != 0) {
+ mInfo = info;
+ mChangeHandler.sendEmptyMessage(change);
+ }
+ }
+
+ public static int getSingleFrameMs(Context context) {
+ return INSTANCE.get(context).getInfo().singleFrameMs;
+ }
+
+ public Info getInfo() {
+ return mInfo;
+ }
+
+ public void addChangeListener(DisplayInfoChangeListener listener) {
+ mListeners.add(listener);
+ }
+
+ public void removeChangeListener(DisplayInfoChangeListener listener) {
+ mListeners.remove(listener);
+ }
+
+ private boolean onChange(Message msg) {
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ mListeners.get(i).onDisplayInfoChanged(mInfo, msg.what);
+ }
+ return true;
+ }
+
+ public static class Info {
+
+ public final int id;
+ public final int rotation;
+ public final int singleFrameMs;
+
+ public final Point realSize;
+ public final Point smallestSize;
+ public final Point largestSize;
+
+ public final DisplayMetrics metrics;
+
+ private Info(Context context) {
+ Display display = context.getSystemService(WindowManager.class).getDefaultDisplay();
+
+ id = display.getDisplayId();
+ rotation = display.getRotation();
+
+ float refreshRate = display.getRefreshRate();
+ singleFrameMs = refreshRate > 0 ? (int) (1000 / refreshRate) : 16;
+
+ realSize = new Point();
+ smallestSize = new Point();
+ largestSize = new Point();
+ display.getRealSize(realSize);
+ display.getCurrentSizeRange(smallestSize, largestSize);
+
+ metrics = new DisplayMetrics();
+ display.getMetrics(metrics);
+ }
+
+ private boolean hasDifferentSize(Info info) {
+ if (!realSize.equals(info.realSize)
+ && !realSize.equals(info.realSize.y, info.realSize.x)) {
+ Log.d(TAG, String.format("Display size changed from %s to %s",
+ info.realSize, realSize));
+ return true;
+ }
+
+ if (!smallestSize.equals(info.smallestSize) || !largestSize.equals(info.largestSize)) {
+ Log.d(TAG, String.format("Available size changed from [%s, %s] to [%s, %s]",
+ smallestSize, largestSize, info.smallestSize, info.largestSize));
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ /**
+ * Interface for listening for display changes
+ */
+ public interface DisplayInfoChangeListener {
+
+ void onDisplayInfoChanged(Info info, int flags);
+ }
+}
diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java
new file mode 100644
index 0000000..4d5ee49
--- /dev/null
+++ b/src/com/android/launcher3/util/Executors.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2008 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.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Various different executors used in Launcher
+ */
+public class Executors {
+
+ // These values are same as that in {@link AsyncTask}.
+ private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
+ private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
+ private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
+ private static final int KEEP_ALIVE = 1;
+
+ /**
+ * An {@link Executor} to be used with async task with no limit on the queue size.
+ */
+ public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
+ CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
+ TimeUnit.SECONDS, new LinkedBlockingQueue<>());
+
+ /**
+ * Returns the executor for running tasks on the main thread.
+ */
+ public static final LooperExecutor MAIN_EXECUTOR =
+ new LooperExecutor(Looper.getMainLooper());
+
+ /**
+ * A background executor for using time sensitive actions where user is waiting for response.
+ */
+ public static final LooperExecutor UI_HELPER_EXECUTOR =
+ new LooperExecutor(createAndStartNewForegroundLooper("UiThreadHelper"));
+
+ /**
+ * Utility method to get a started handler thread statically
+ */
+ public static Looper createAndStartNewLooper(String name) {
+ return createAndStartNewLooper(name, Process.THREAD_PRIORITY_DEFAULT);
+ }
+
+ /**
+ * Utility method to get a started handler thread statically with the provided priority
+ */
+ public static Looper createAndStartNewLooper(String name, int priority) {
+ HandlerThread thread = new HandlerThread(name, priority);
+ thread.start();
+ return thread.getLooper();
+ }
+
+ /**
+ * Similar to {@link #createAndStartNewLooper(String)}, but starts the thread with
+ * foreground priority.
+ * Think before using
+ */
+ public static Looper createAndStartNewForegroundLooper(String name) {
+ return createAndStartNewLooper(name, Process.THREAD_PRIORITY_FOREGROUND);
+ }
+
+ /**
+ * Executor used for running Launcher model related tasks (eg loading icons or updated db)
+ */
+ public static final LooperExecutor MODEL_EXECUTOR =
+ new LooperExecutor(createAndStartNewLooper("launcher-loader"));
+}
diff --git a/src/com/android/launcher3/util/IOUtils.java b/src/com/android/launcher3/util/IOUtils.java
index f95f74d..4a4a5ca 100644
--- a/src/com/android/launcher3/util/IOUtils.java
+++ b/src/com/android/launcher3/util/IOUtils.java
@@ -17,10 +17,13 @@
package com.android.launcher3.util;
import android.content.Context;
+import android.util.Log;
+import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -35,6 +38,7 @@
public class IOUtils {
private static final int BUF_SIZE = 0x1000; // 4K
+ private static final String TAG = "IOUtils";
public static byte[] toByteArray(File file) throws IOException {
try (InputStream in = new FileInputStream(file)) {
@@ -77,4 +81,16 @@
}
return file.getAbsolutePath();
}
+
+ public static void closeSilently(Closeable c) {
+ if (c != null) {
+ try {
+ c.close();
+ } catch (IOException e) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD) {
+ Log.d(TAG, "Error closing", e);
+ }
+ }
+ }
+ }
}
diff --git a/src/com/android/launcher3/util/IntArray.java b/src/com/android/launcher3/util/IntArray.java
index d2a551f..7252f7a 100644
--- a/src/com/android/launcher3/util/IntArray.java
+++ b/src/com/android/launcher3/util/IntArray.java
@@ -17,6 +17,7 @@
package com.android.launcher3.util;
import java.util.Arrays;
+import java.util.StringTokenizer;
/**
* Copy of the platform hidden implementation of android.util.IntArray.
@@ -248,6 +249,17 @@
return b.toString();
}
+ public static IntArray fromConcatString(String concatString) {
+ StringTokenizer tokenizer = new StringTokenizer(concatString, ",");
+ int[] array = new int[tokenizer.countTokens()];
+ int count = 0;
+ while (tokenizer.hasMoreTokens()) {
+ array[count] = Integer.parseInt(tokenizer.nextToken().trim());
+ count++;
+ }
+ return new IntArray(array, array.length);
+ }
+
/**
* Throws {@link ArrayIndexOutOfBoundsException} if the index is out of bounds.
*
diff --git a/src/com/android/launcher3/util/LooperExecutor.java b/src/com/android/launcher3/util/LooperExecutor.java
index cc07469..8ac600f 100644
--- a/src/com/android/launcher3/util/LooperExecutor.java
+++ b/src/com/android/launcher3/util/LooperExecutor.java
@@ -16,7 +16,9 @@
package com.android.launcher3.util;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
+import android.os.Process;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
@@ -47,6 +49,13 @@
}
/**
+ * Same as execute, but never runs the action inline.
+ */
+ public void post(Runnable runnable) {
+ mHandler.post(runnable);
+ }
+
+ /**
* Not supported and throws an exception when used.
*/
@Override
@@ -79,7 +88,31 @@
*/
@Override
@Deprecated
- public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException {
+ public boolean awaitTermination(long l, TimeUnit timeUnit) {
throw new UnsupportedOperationException();
}
+
+ /**
+ * Returns the thread for this executor
+ */
+ public Thread getThread() {
+ return mHandler.getLooper().getThread();
+ }
+
+ /**
+ * Returns the looper for this executor
+ */
+ public Looper getLooper() {
+ return mHandler.getLooper();
+ }
+
+ /**
+ * Set the priority of a thread, based on Linux priorities.
+ * @param priority Linux priority level, from -20 for highest scheduling priority
+ * to 19 for lowest scheduling priority.
+ * @see Process#setThreadPriority(int, int)
+ */
+ public void setThreadPriority(int priority) {
+ Process.setThreadPriority(((HandlerThread) getThread()).getThreadId(), priority);
+ }
}
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index 2ee0328..fe9c2c4 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -15,15 +15,17 @@
*/
package com.android.launcher3.util;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
import android.content.Context;
import android.os.Looper;
-import com.android.launcher3.MainThreadExecutor;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.util.ResourceBasedOverride.Overrides;
import java.util.concurrent.ExecutionException;
-import androidx.annotation.VisibleForTesting;
-
/**
* Utility class for defining singletons which are initiated on main thread.
*/
@@ -42,7 +44,7 @@
mValue = mProvider.get(context.getApplicationContext());
} else {
try {
- return new MainThreadExecutor().submit(() -> get(context)).get();
+ return MAIN_EXECUTOR.submit(() -> get(context)).get();
} catch (InterruptedException|ExecutionException e) {
throw new RuntimeException(e);
}
@@ -60,6 +62,14 @@
mValue = value;
}
+ /**
+ * Initializes a provider based on resource overrides
+ */
+ public static <T extends ResourceBasedOverride> MainThreadInitializedObject<T> forOverride(
+ Class<T> clazz, int resourceId) {
+ return new MainThreadInitializedObject<>(c -> Overrides.getObject(clazz, c, resourceId));
+ }
+
public interface ObjectProvider<T> {
T get(Context context);
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 7d3a941..e97adb5 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -24,9 +24,11 @@
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
@@ -35,6 +37,7 @@
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.widget.Toast;
import com.android.launcher3.AppInfo;
@@ -220,4 +223,76 @@
packageFilter.addDataSchemeSpecificPart(pkg, PatternMatcher.PATTERN_LITERAL);
return packageFilter;
}
+
+ public static boolean isSystemApp(Context context, Intent intent) {
+ PackageManager pm = context.getPackageManager();
+ ComponentName cn = intent.getComponent();
+ String packageName = null;
+ if (cn == null) {
+ ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ if ((info != null) && (info.activityInfo != null)) {
+ packageName = info.activityInfo.packageName;
+ }
+ } else {
+ packageName = cn.getPackageName();
+ }
+ if (packageName == null) {
+ packageName = intent.getPackage();
+ }
+ if (packageName != null) {
+ try {
+ PackageInfo info = pm.getPackageInfo(packageName, 0);
+ return (info != null) && (info.applicationInfo != null) &&
+ ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Finds a system apk which had a broadcast receiver listening to a particular action.
+ * @param action intent action used to find the apk
+ * @return a pair of apk package name and the resources.
+ */
+ public static Pair<String, Resources> findSystemApk(String action, PackageManager pm) {
+ final Intent intent = new Intent(action);
+ for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) {
+ if (info.activityInfo != null &&
+ (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ final String packageName = info.activityInfo.packageName;
+ try {
+ final Resources res = pm.getResourcesForApplication(packageName);
+ return Pair.create(packageName, res);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Failed to find resources for " + packageName);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the intent is a valid launch intent for a launcher activity of an app.
+ * This is used to identify shortcuts which are different from the ones exposed by the
+ * applications' manifest file.
+ *
+ * @param launchIntent The intent that will be launched when the shortcut is clicked.
+ */
+ public static boolean isLauncherAppTarget(Intent launchIntent) {
+ if (launchIntent != null
+ && Intent.ACTION_MAIN.equals(launchIntent.getAction())
+ && launchIntent.getComponent() != null
+ && launchIntent.getCategories() != null
+ && launchIntent.getCategories().size() == 1
+ && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
+ && TextUtils.isEmpty(launchIntent.getDataString())) {
+ // An app target can either have no extra or have ItemInfo.EXTRA_PROFILE.
+ Bundle extras = launchIntent.getExtras();
+ return extras == null || extras.keySet().isEmpty();
+ }
+ return false;
+ }
}
diff --git a/src/com/android/launcher3/util/PackageUserKey.java b/src/com/android/launcher3/util/PackageUserKey.java
index 1ce2822..f243ca6 100644
--- a/src/com/android/launcher3/util/PackageUserKey.java
+++ b/src/com/android/launcher3/util/PackageUserKey.java
@@ -3,8 +3,9 @@
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
import java.util.Arrays;
@@ -15,7 +16,9 @@
public UserHandle mUser;
private int mHashCode;
+ @Nullable
public static PackageUserKey fromItemInfo(ItemInfo info) {
+ if (info.getTargetComponent() == null) return null;
return new PackageUserKey(info.getTargetComponent().getPackageName(), info.user);
}
@@ -27,7 +30,7 @@
update(packageName, user);
}
- private void update(String packageName, UserHandle user) {
+ public void update(String packageName, UserHandle user) {
mPackageName = packageName;
mUser = user;
mHashCode = Arrays.hashCode(new Object[] {packageName, user});
@@ -38,7 +41,8 @@
* @return Whether this PackageUserKey was successfully updated - it shouldn't be used if not.
*/
public boolean updateFromItemInfo(ItemInfo info) {
- if (DeepShortcutManager.supportsShortcuts(info)) {
+ if (info.getTargetComponent() == null) return false;
+ if (ShortcutUtil.supportsShortcuts(info)) {
update(info.getTargetComponent().getPackageName(), info.user);
return true;
}
diff --git a/src/com/android/launcher3/util/Preconditions.java b/src/com/android/launcher3/util/Preconditions.java
index 7ab0d31..ed66422 100644
--- a/src/com/android/launcher3/util/Preconditions.java
+++ b/src/com/android/launcher3/util/Preconditions.java
@@ -16,9 +16,10 @@
package com.android.launcher3.util;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
import android.os.Looper;
-import com.android.launcher3.LauncherModel;
import com.android.launcher3.config.FeatureFlags;
/**
@@ -33,7 +34,7 @@
}
public static void assertWorkerThread() {
- if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(LauncherModel.getWorkerLooper())) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(MODEL_EXECUTOR.getLooper())) {
throw new IllegalStateException();
}
}
diff --git a/src/com/android/launcher3/util/SafeCloseable.java b/src/com/android/launcher3/util/SafeCloseable.java
new file mode 100644
index 0000000..ba8ee04
--- /dev/null
+++ b/src/com/android/launcher3/util/SafeCloseable.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 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;
+
+/**
+ * Extension of closeable which does not throw an exception
+ */
+public interface SafeCloseable extends AutoCloseable {
+
+ @Override
+ void close();
+}
diff --git a/src/com/android/launcher3/util/ShortcutUtil.java b/src/com/android/launcher3/util/ShortcutUtil.java
new file mode 100644
index 0000000..af99713
--- /dev/null
+++ b/src/com/android/launcher3/util/ShortcutUtil.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 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 com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.shortcuts.ShortcutKey;
+
+public class ShortcutUtil {
+ /**
+ * Returns true when we should show shortcut menu for the item.
+ */
+ public static boolean supportsShortcuts(ItemInfo info) {
+ return isActive(info) && (isApp(info) || isPinnedShortcut(info));
+ }
+
+ /**
+ * Returns true when we should show depp shortcuts in shortcut menu for the item.
+ */
+ public static boolean supportsDeepShortcuts(ItemInfo info) {
+ return isActive(info) && isApp(info);
+ }
+
+ /**
+ * Returns the shortcut id if the item is a pinned shortcut.
+ */
+ public static String getShortcutIdIfPinnedShortcut(ItemInfo info) {
+ return isActive(info) && isPinnedShortcut(info)
+ ? ShortcutKey.fromItemInfo(info).getId() : null;
+ }
+
+ /**
+ * Returns the person keys associated with the item. (Has no function right now.)
+ */
+ public static String[] getPersonKeysIfPinnedShortcut(ItemInfo info) {
+ return isActive(info) && isPinnedShortcut(info)
+ ? ((WorkspaceItemInfo) info).getPersonKeys() : Utilities.EMPTY_STRING_ARRAY;
+ }
+
+ /**
+ * Returns true if the item is a deep shortcut.
+ */
+ public static boolean isDeepShortcut(ItemInfo info) {
+ return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+ && info instanceof WorkspaceItemInfo;
+ }
+
+ private static boolean isActive(ItemInfo info) {
+ boolean isLoading = info instanceof WorkspaceItemInfo
+ && ((WorkspaceItemInfo) info).hasPromiseIconUi();
+ return !isLoading && !info.isDisabled() && !FeatureFlags.GO_DISABLE_WIDGETS;
+ }
+
+ private static boolean isApp(ItemInfo info) {
+ return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+ }
+
+ private static boolean isPinnedShortcut(ItemInfo info) {
+ return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+ && info.container != ItemInfo.NO_ID
+ && info instanceof WorkspaceItemInfo;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java
index cc442f9..f8d1632 100644
--- a/src/com/android/launcher3/util/UiThreadHelper.java
+++ b/src/com/android/launcher3/util/UiThreadHelper.java
@@ -15,14 +15,13 @@
*/
package com.android.launcher3.util;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
import android.app.Activity;
import android.content.Context;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.Looper;
import android.os.Message;
-import android.os.Process;
import android.view.inputmethod.InputMethodManager;
/**
@@ -30,25 +29,15 @@
*/
public class UiThreadHelper {
- private static HandlerThread sHandlerThread;
private static Handler sHandler;
private static final int MSG_HIDE_KEYBOARD = 1;
private static final int MSG_SET_ORIENTATION = 2;
private static final int MSG_RUN_COMMAND = 3;
- public static Looper getBackgroundLooper() {
- if (sHandlerThread == null) {
- sHandlerThread =
- new HandlerThread("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND);
- sHandlerThread.start();
- }
- return sHandlerThread.getLooper();
- }
-
private static Handler getHandler(Context context) {
if (sHandler == null) {
- sHandler = new Handler(getBackgroundLooper(),
+ sHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(),
new UiCallbacks(context.getApplicationContext()));
}
return sHandler;
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
new file mode 100644
index 0000000..04741a1
--- /dev/null
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 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 static android.os.VibrationEffect.createPredefined;
+import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Build;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.provider.Settings;
+
+/**
+ * Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
+ */
+@TargetApi(Build.VERSION_CODES.Q)
+public class VibratorWrapper {
+
+ public static final MainThreadInitializedObject<VibratorWrapper> INSTANCE =
+ new MainThreadInitializedObject<>(VibratorWrapper::new);
+
+ private static final VibrationEffect EFFECT_CLICK =
+ createPredefined(VibrationEffect.EFFECT_CLICK);
+
+ /**
+ * Haptic when entering overview.
+ */
+ public static final VibrationEffect OVERVIEW_HAPTIC = EFFECT_CLICK;
+
+ private final Vibrator mVibrator;
+ private final boolean mHasVibrator;
+
+ private boolean mIsHapticFeedbackEnabled;
+
+ public VibratorWrapper(Context context) {
+ mVibrator = context.getSystemService(Vibrator.class);
+ mHasVibrator = mVibrator.hasVibrator();
+ if (mHasVibrator) {
+ final ContentResolver resolver = context.getContentResolver();
+ mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver);
+ final ContentObserver observer = new ContentObserver(MAIN_EXECUTOR.getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver);
+ }
+ };
+ resolver.registerContentObserver(Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED),
+ false /* notifyForDescendents */, observer);
+ } else {
+ mIsHapticFeedbackEnabled = false;
+ }
+ }
+
+ private boolean isHapticFeedbackEnabled(ContentResolver resolver) {
+ return Settings.System.getInt(resolver, HAPTIC_FEEDBACK_ENABLED, 0) == 1;
+ }
+
+ /** Vibrates with the given effect if haptic feedback is available and enabled. */
+ public void vibrate(VibrationEffect vibrationEffect) {
+ if (mHasVibrator && mIsHapticFeedbackEnabled) {
+ UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect));
+ }
+ }
+}
diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
index acce308..5a131c8 100644
--- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java
+++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
@@ -16,13 +16,14 @@
package com.android.launcher3.util;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
import android.os.Process;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewTreeObserver.OnDrawListener;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherModel;
import java.util.ArrayList;
import java.util.concurrent.Executor;
@@ -54,7 +55,9 @@
mLoadAnimationCompleted = true;
}
- attachObserver();
+ if (mAttachedView.isAttachedToWindow()) {
+ attachObserver();
+ }
}
private void attachObserver() {
@@ -66,7 +69,7 @@
@Override
public void execute(Runnable command) {
mTasks.add(command);
- LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
@Override
@@ -108,7 +111,7 @@
if (mLauncher != null) {
mLauncher.clearPendingExecutor(this);
}
- LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_DEFAULT);
+ MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
}
protected boolean isCompleted() {
diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java
index 8af048d..5b33f18 100644
--- a/src/com/android/launcher3/util/ViewPool.java
+++ b/src/com/android/launcher3/util/ViewPool.java
@@ -21,12 +21,12 @@
import android.view.View;
import android.view.ViewGroup;
-import com.android.launcher3.util.ViewPool.Reusable;
-
import androidx.annotation.AnyThread;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import com.android.launcher3.util.ViewPool.Reusable;
+
/**
* Utility class to maintain a pool of reusable views.
* During initialization, views are inflated on the background thread.
@@ -58,14 +58,18 @@
Preconditions.assertUIThread();
Handler handler = new Handler();
+ // LayoutInflater is not thread save as it maintains a global variable 'mConstructorArgs'.
+ // Create a different copy to use on the background thread.
+ LayoutInflater inflater = mInflater.cloneInContext(mInflater.getContext());
+
// Inflate views on a non looper thread. This allows us to catch errors like calling
// "new Handler()" in constructor easily.
new Thread(() -> {
for (int i = 0; i < initialSize; i++) {
- T view = inflateNewView();
+ T view = inflateNewView(inflater);
handler.post(() -> addToPool(view));
}
- }).start();
+ }, "ViewPool-init").start();
}
@UiThread
@@ -94,12 +98,12 @@
mCurrentSize--;
return (T) mPool[mCurrentSize];
}
- return inflateNewView();
+ return inflateNewView(mInflater);
}
@AnyThread
- private T inflateNewView() {
- return (T) mInflater.inflate(mLayoutId, mParent, false);
+ private T inflateNewView(LayoutInflater inflater) {
+ return (T) inflater.inflate(mLayoutId, mParent, false);
}
/**
diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
index 5c24687..2ad80cf 100644
--- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
+++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
@@ -1,5 +1,7 @@
package com.android.launcher3.util;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -198,7 +200,7 @@
private float mOffsetX;
public OffsetHandler(Context context) {
- super(UiThreadHelper.getBackgroundLooper());
+ super(UI_HELPER_EXECUTOR.getLooper());
mInterpolator = Interpolators.DEACCEL_1_5;
mWM = WallpaperManager.getInstance(context);
}
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index f948beb..195a77a 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -32,13 +32,14 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.BaseSwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
/**
* Extension of AbstractFloatingView with common methods for sliding in from bottom
*/
public abstract class AbstractSlideInView extends AbstractFloatingView
- implements SwipeDetector.Listener {
+ implements SingleAxisSwipeDetector.Listener {
protected static Property<AbstractSlideInView, Float> TRANSLATION_SHIFT =
new Property<AbstractSlideInView, Float>(Float.class, "translationShift") {
@@ -57,7 +58,7 @@
protected static final float TRANSLATION_SHIFT_OPENED = 0f;
protected final Launcher mLauncher;
- protected final SwipeDetector mSwipeDetector;
+ protected final SingleAxisSwipeDetector mSwipeDetector;
protected final ObjectAnimator mOpenCloseAnimator;
protected View mContent;
@@ -73,7 +74,8 @@
mLauncher = Launcher.getLauncher(context);
mScrollInterpolator = Interpolators.SCROLL_CUBIC;
- mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL);
+ mSwipeDetector = new SingleAxisSwipeDetector(context, this,
+ SingleAxisSwipeDetector.VERTICAL);
mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
@@ -97,7 +99,7 @@
}
int directionsToDetectScroll = mSwipeDetector.isIdleState() ?
- SwipeDetector.DIRECTION_NEGATIVE : 0;
+ SingleAxisSwipeDetector.DIRECTION_NEGATIVE : 0;
mSwipeDetector.setDetectableScrollConditions(
directionsToDetectScroll, false);
mSwipeDetector.onTouchEvent(ev);
@@ -122,7 +124,7 @@
return mIsOpen && mOpenCloseAnimator.isRunning();
}
- /* SwipeDetector.Listener */
+ /* SingleAxisSwipeDetector.Listener */
@Override
public void onDragStart(boolean start) { }
@@ -136,32 +138,32 @@
}
@Override
- public void onDragEnd(float velocity, boolean fling) {
- if ((fling && velocity > 0) || mTranslationShift > 0.5f) {
+ public void onDragEnd(float velocity) {
+ if ((mSwipeDetector.isFling(velocity) && velocity > 0) || mTranslationShift > 0.5f) {
mScrollInterpolator = scrollInterpolatorForVelocity(velocity);
- mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(
+ mOpenCloseAnimator.setDuration(BaseSwipeDetector.calculateDuration(
velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift));
close(true);
} else {
mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(
TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
mOpenCloseAnimator.setDuration(
- SwipeDetector.calculateDuration(velocity, mTranslationShift))
+ BaseSwipeDetector.calculateDuration(velocity, mTranslationShift))
.setInterpolator(Interpolators.DEACCEL);
mOpenCloseAnimator.start();
}
}
protected void handleClose(boolean animate, long defaultDuration) {
- if (mIsOpen && !animate) {
+ if (!mIsOpen) {
+ return;
+ }
+ if (!animate) {
mOpenCloseAnimator.cancel();
setTranslationShift(TRANSLATION_SHIFT_CLOSED);
onCloseComplete();
return;
}
- if (!mIsOpen) {
- return;
- }
mOpenCloseAnimator.setValues(
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_CLOSED));
mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 15f2724..a4f6a8e 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -20,11 +20,16 @@
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
-import static com.android.launcher3.Utilities.shouldDisableGestures;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import android.annotation.TargetApi;
+import android.app.WallpaperInfo;
+import android.app.WallpaperManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -40,8 +45,11 @@
import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.MultiValueAlpha;
@@ -101,6 +109,14 @@
protected final T mActivity;
private final MultiValueAlpha mMultiValueAlpha;
+ private final WallpaperManager mWallpaperManager;
+ private final BroadcastReceiver mWallpaperChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ onWallpaperChanged();
+ }
+ };
+ private final String[] mWallpapersWithoutSysuiScrims;
// All the touch controllers for the view
protected TouchController[] mControllers;
@@ -111,10 +127,15 @@
private TouchCompleteListener mTouchCompleteListener;
+ protected boolean mAllowSysuiScrims = true;
+
public BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount) {
super(context, attrs);
mActivity = (T) ActivityContext.lookupContext(context);
mMultiValueAlpha = new MultiValueAlpha(this, alphaChannelCount);
+ mWallpaperManager = context.getSystemService(WallpaperManager.class);
+ mWallpapersWithoutSysuiScrims = getResources().getStringArray(
+ R.array.live_wallpapers_remove_sysui_scrims);
}
/**
@@ -152,8 +173,6 @@
}
private TouchController findControllerToHandleTouch(MotionEvent ev) {
- if (shouldDisableGestures(ev)) return null;
-
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
return topView;
@@ -173,10 +192,8 @@
// Only look for controllers if we are not dispatching from gesture area and proxy is
// not active
mActiveController = findControllerToHandleTouch(ev);
-
- if (mActiveController != null) return true;
}
- return false;
+ return mActiveController != null;
}
@Override
@@ -223,17 +240,13 @@
// This can happen if something goes wrong during a state change/transition.
AbstractFloatingView floatingView = (AbstractFloatingView) child;
if (floatingView.isOpen()) {
- postDelayed(() -> floatingView.close(false), SINGLE_FRAME_MS);
+ postDelayed(() -> floatingView.close(false), getSingleFrameMs(getContext()));
}
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
- if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_DRAG_TAG,
- "onTouchEvent " + ev);
- }
int action = ev.getAction();
if (action == ACTION_UP || action == ACTION_CANCEL) {
if (mTouchCompleteListener != null) {
@@ -243,10 +256,6 @@
}
if (mActiveController != null) {
- if (TestProtocol.sDebugTracing) {
- android.util.Log.d(TestProtocol.NO_DRAG_TAG,
- "onTouchEvent 1");
- }
return mActiveController.onControllerTouchEvent(ev);
} else {
// In case no child view handled the touch event, we may not get onIntercept anymore
@@ -256,9 +265,6 @@
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_START_TAG, "BaseDragLayer.dispatchTouchEvent " + ev);
- }
switch (ev.getAction()) {
case ACTION_DOWN: {
float x = ev.getX();
@@ -277,6 +283,10 @@
}
case ACTION_CANCEL:
case ACTION_UP:
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE,
+ "BaseDragLayer.ACTION_UP/CANCEL " + ev);
+ }
mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
mTouchDispatchState &= ~TOUCH_DISPATCHING_VIEW;
break;
@@ -475,7 +485,7 @@
}
public void dump(String prefix, PrintWriter writer) {
- writer.println(prefix + "DragLayer");
+ writer.println(prefix + "DragLayer:");
if (mActiveController != null) {
writer.println(prefix + "\tactiveController: " + mActiveController);
mActiveController.dump(prefix + "\t", writer);
@@ -525,4 +535,47 @@
}
return super.dispatchApplyWindowInsets(insets);
}
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mActivity.registerReceiver(mWallpaperChangeReceiver,
+ new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
+ onWallpaperChanged();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mActivity.unregisterReceiver(mWallpaperChangeReceiver);
+ }
+
+ private void onWallpaperChanged() {
+ WallpaperInfo newWallpaperInfo = mWallpaperManager.getWallpaperInfo();
+ boolean oldAllowSysuiScrims = mAllowSysuiScrims;
+ mAllowSysuiScrims = computeAllowSysuiScrims(newWallpaperInfo);
+ if (mAllowSysuiScrims != oldAllowSysuiScrims) {
+ // Reapply insets so scrim can be removed or re-added if necessary.
+ setInsets(mInsets);
+ }
+ }
+
+ /**
+ * Determines whether we can scrim the status bar and nav bar for the given wallpaper by
+ * checking against a list of live wallpapers that we don't show the scrims on.
+ */
+ private boolean computeAllowSysuiScrims(@Nullable WallpaperInfo newWallpaperInfo) {
+ if (newWallpaperInfo == null) {
+ // New wallpaper is static, not live. Thus, blacklist isn't applicable.
+ return true;
+ }
+ ComponentName newWallpaper = newWallpaperInfo.getComponent();
+ for (String wallpaperWithoutScrim : mWallpapersWithoutSysuiScrims) {
+ if (newWallpaper.equals(ComponentName.unflattenFromString(wallpaperWithoutScrim))) {
+ // New wallpaper is blacklisted from showing a scrim.
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index ab4b576..49d94f0 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -41,7 +42,6 @@
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.CancellationSignal;
-import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -50,11 +50,17 @@
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ImageView;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.InsettableFrameLayout.LayoutParams;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherModel;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragLayer;
@@ -66,13 +72,6 @@
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.shortcuts.DeepShortcutView;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
/**
* A view that is created to look like another view with the purpose of creating fluid animations.
*/
@@ -129,6 +128,7 @@
private final Launcher mLauncher;
private final int mBlurSizeOutline;
+ private final boolean mIsRtl;
private boolean mIsVerticalBarLayout = false;
private boolean mIsAdaptiveIcon = false;
@@ -174,6 +174,7 @@
mLauncher = Launcher.getLauncher(context);
mBlurSizeOutline = getResources().getDimensionPixelSize(
R.dimen.blur_size_medium_outline);
+ mIsRtl = Utilities.isRtl(getResources());
mListenerView = new ListenerView(context, attrs);
mFgSpringX = new SpringAnimation(this, mFgTransXProperty)
@@ -213,7 +214,10 @@
setAlpha(alpha);
LayoutParams lp = (LayoutParams) getLayoutParams();
- float dX = rect.left - lp.leftMargin;
+ float dX = mIsRtl
+ ? rect.left
+ - (mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width)
+ : rect.left - lp.getMarginStart();
float dY = rect.top - lp.topMargin;
setTranslationX(dX);
setTranslationY(dY);
@@ -323,14 +327,18 @@
mPositionOut.set(position);
lp.ignoreInsets = true;
// Position the floating view exactly on top of the original
- lp.leftMargin = Math.round(position.left);
lp.topMargin = Math.round(position.top);
-
+ if (mIsRtl) {
+ lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - position.right));
+ } else {
+ lp.setMarginStart(Math.round(position.left));
+ }
// Set the properties here already to make sure they are available when running the first
// animation frame.
- layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin
- + lp.height);
-
+ int left = mIsRtl
+ ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width
+ : lp.leftMargin;
+ layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
}
/**
@@ -514,8 +522,11 @@
} else {
lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
}
- layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin
- + lp.height);
+
+ int left = mIsRtl
+ ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width
+ : lp.leftMargin;
+ layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
float scale = Math.max((float) lp.height / originalHeight,
(float) lp.width / originalWidth);
@@ -549,13 +560,8 @@
* Checks if the icon result is loaded. If true, we set the icon immediately. Else, we add a
* callback to set the icon once the icon result is loaded.
*/
- private void checkIconResult(View originalView, boolean isOpening) {
+ private void checkIconResult(View originalView) {
CancellationSignal cancellationSignal = new CancellationSignal();
- if (!isOpening) {
- // Hide immediately since the floating view starts at a different location.
- originalView.setVisibility(INVISIBLE);
- cancellationSignal.setOnCancelListener(() -> originalView.setVisibility(VISIBLE));
- }
if (mIconLoadResult == null) {
Log.w(TAG, "No icon load result found in checkIconResult");
@@ -566,9 +572,7 @@
if (mIconLoadResult.isIconLoaded) {
setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge,
mIconLoadResult.iconOffset);
- if (isOpening) {
- originalView.setVisibility(INVISIBLE);
- }
+ hideOriginalView(originalView);
} else {
mIconLoadResult.onIconLoaded = () -> {
if (cancellationSignal.isCanceled()) {
@@ -577,16 +581,23 @@
setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge,
mIconLoadResult.iconOffset);
-
- // Delay swapping views until the icon is loaded to prevent a flash.
setVisibility(VISIBLE);
- originalView.setVisibility(INVISIBLE);
+ hideOriginalView(originalView);
};
mLoadIconSignal = cancellationSignal;
}
}
}
+ private void hideOriginalView(View originalView) {
+ if (originalView instanceof IconLabelDotView) {
+ ((IconLabelDotView) originalView).setIconVisible(false);
+ ((IconLabelDotView) originalView).setForceHideDot(true);
+ } else {
+ originalView.setVisibility(INVISIBLE);
+ }
+ }
+
private void setBackgroundDrawableBounds(float scale) {
sTmpRect.set(mFinalDrawableBounds);
Utilities.scaleRectAboutCenter(sTmpRect, scale);
@@ -656,8 +667,10 @@
canvas.restoreToCount(count);
}
- public void onListenerViewClosed() {
- // Fast finish here.
+ public void fastFinish() {
+ if (mLoadIconSignal != null) {
+ mLoadIconSignal.cancel();
+ }
if (mEndRunnable != null) {
mEndRunnable.run();
mEndRunnable = null;
@@ -673,6 +686,10 @@
if (mIconLoadResult != null && mIconLoadResult.isIconLoaded) {
setVisibility(View.VISIBLE);
}
+ if (!mIsOpening) {
+ // When closing an app, we want the item on the workspace to be invisible immediately
+ hideOriginalView(mOriginalIcon);
+ }
}
@Override
@@ -704,8 +721,8 @@
*/
@UiThread
public static IconLoadResult fetchIcon(Launcher l, View v, ItemInfo info, boolean isOpening) {
- IconLoadResult result = new IconLoadResult();
- new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> {
+ IconLoadResult result = new IconLoadResult(info);
+ MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> {
RectF position = new RectF();
getLocationBoundsForView(l, v, isOpening, position);
getIconResult(l, v, info, position, result);
@@ -733,10 +750,13 @@
// Get the drawable on the background thread
boolean shouldLoadIcon = originalView.getTag() instanceof ItemInfo && hideOriginal;
- view.mIconLoadResult = sIconLoadResult;
- if (shouldLoadIcon && view.mIconLoadResult == null) {
- view.mIconLoadResult = fetchIcon(launcher, originalView,
- (ItemInfo) originalView.getTag(), isOpening);
+ if (shouldLoadIcon) {
+ if (sIconLoadResult != null && sIconLoadResult.itemInfo == originalView.getTag()) {
+ view.mIconLoadResult = sIconLoadResult;
+ } else {
+ view.mIconLoadResult = fetchIcon(launcher, originalView,
+ (ItemInfo) originalView.getTag(), isOpening);
+ }
}
sIconLoadResult = null;
@@ -748,23 +768,23 @@
// Match the position of the original view.
view.matchPositionOf(launcher, originalView, isOpening, positionOut);
- // Must be called after matchPositionOf so that we know what size to load.
- if (shouldLoadIcon) {
- view.checkIconResult(originalView, isOpening);
- }
-
// We need to add it to the overlay, but keep it invisible until animation starts..
view.setVisibility(INVISIBLE);
parent.addView(view);
dragLayer.addView(view.mListenerView);
- view.mListenerView.setListener(view::onListenerViewClosed);
+ view.mListenerView.setListener(view::fastFinish);
view.mEndRunnable = () -> {
view.mEndRunnable = null;
if (hideOriginal) {
if (isOpening) {
- originalView.setVisibility(VISIBLE);
+ if (originalView instanceof BubbleTextView) {
+ ((BubbleTextView) originalView).setIconVisible(true);
+ ((BubbleTextView) originalView).setForceHideDot(false);
+ } else {
+ originalView.setVisibility(VISIBLE);
+ }
view.finish(dragLayer);
} else {
view.mFadeAnimatorSet = view.createFadeAnimation(originalView, dragLayer);
@@ -774,6 +794,14 @@
view.finish(dragLayer);
}
};
+
+ // Must be called after matchPositionOf so that we know what size to load.
+ // Must be called after the fastFinish listener and end runnable is created so that
+ // the icon is not left in a hidden state.
+ if (shouldLoadIcon) {
+ view.checkIconResult(originalView);
+ }
+
return view;
}
@@ -792,38 +820,34 @@
}
});
- if (mBadge != null && !(mOriginalIcon instanceof FolderIcon)) {
+ if (mBadge != null) {
ObjectAnimator badgeFade = ObjectAnimator.ofInt(mBadge, DRAWABLE_ALPHA, 255);
badgeFade.addUpdateListener(valueAnimator -> invalidate());
fade.play(badgeFade);
}
- if (originalView instanceof BubbleTextView) {
- BubbleTextView btv = (BubbleTextView) originalView;
- btv.forceHideDot(true);
+ if (originalView instanceof IconLabelDotView) {
+ IconLabelDotView view = (IconLabelDotView) originalView;
fade.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- btv.forceHideDot(false);
+ view.setIconVisible(true);
+ view.setForceHideDot(false);
}
});
}
- if (originalView instanceof FolderIcon) {
- FolderIcon folderIcon = (FolderIcon) originalView;
- folderIcon.setBackgroundVisible(false);
- folderIcon.getFolderName().setTextVisibility(false);
- fade.play(folderIcon.getFolderName().createTextAlphaAnimator(true));
+ if (originalView instanceof BubbleTextView) {
+ BubbleTextView btv = (BubbleTextView) originalView;
fade.addListener(new AnimatorListenerAdapter() {
@Override
- public void onAnimationEnd(Animator animation) {
- folderIcon.setBackgroundVisible(true);
- if (folderIcon.hasDot()) {
- folderIcon.animateDotScale(0, 1f);
- }
+ public void onAnimationStart(Animator animation) {
+ btv.setIconVisible(true);
+ btv.setForceHideDot(true);
}
});
- } else {
+ fade.play(ObjectAnimator.ofInt(btv.getIcon(), DRAWABLE_ALPHA, 0, 255));
+ } else if (!(originalView instanceof FolderIcon)) {
fade.play(ObjectAnimator.ofFloat(originalView, ALPHA, 0f, 1f));
}
@@ -878,10 +902,15 @@
}
private static class IconLoadResult {
+ final ItemInfo itemInfo;
Drawable drawable;
Drawable badge;
int iconOffset;
Runnable onIconLoaded;
boolean isIconLoaded;
+
+ public IconLoadResult(ItemInfo itemInfo) {
+ this.itemInfo = itemInfo;
+ }
}
}
diff --git a/src/com/android/launcher3/views/IconLabelDotView.java b/src/com/android/launcher3/views/IconLabelDotView.java
new file mode 100644
index 0000000..057caaf
--- /dev/null
+++ b/src/com/android/launcher3/views/IconLabelDotView.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2019 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;
+
+/**
+ * A view that has an icon, label, and notification dot.
+ */
+public interface IconLabelDotView {
+ void setIconVisible(boolean visible);
+ void setForceHideDot(boolean hide);
+}
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 63f7427..465df44 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -18,10 +18,8 @@
import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_FLAVOR;
import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_OFFSET;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ResolveInfo;
import android.graphics.Rect;
import android.graphics.RectF;
import android.text.TextUtils;
@@ -33,6 +31,9 @@
import android.view.View.OnLongClickListener;
import android.widget.Toast;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -46,7 +47,6 @@
import java.util.ArrayList;
import java.util.List;
-import androidx.annotation.VisibleForTesting;
/**
* Popup shown on long pressing an empty space in launcher
@@ -169,16 +169,17 @@
}
public static boolean onWidgetsClicked(View view) {
- return openWidgets(Launcher.getLauncher(view.getContext()));
+ return openWidgets(Launcher.getLauncher(view.getContext())) != null;
}
- public static boolean openWidgets(Launcher launcher) {
+ /** Returns WidgetsFullSheet that was opened, or null if nothing was opened. */
+ @Nullable
+ public static WidgetsFullSheet openWidgets(Launcher launcher) {
if (launcher.getPackageManager().isSafeMode()) {
Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
- return false;
+ return null;
} else {
- WidgetsFullSheet.show(launcher, true /* animated */);
- return true;
+ return WidgetsFullSheet.show(launcher, true /* animated */);
}
}
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index c360117..9f59d78 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -18,14 +18,14 @@
import static android.content.Context.ACCESSIBILITY_SERVICE;
import static android.view.MotionEvent.ACTION_DOWN;
+import static androidx.core.graphics.ColorUtils.compositeColors;
+
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
-import static androidx.core.graphics.ColorUtils.compositeColors;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.Keyframe;
@@ -47,6 +47,13 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
+import androidx.customview.widget.ExploreByTouchHelper;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
@@ -62,15 +69,10 @@
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.widget.WidgetsFullSheet;
import java.util.List;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
-import androidx.customview.widget.ExploreByTouchHelper;
/**
* Simple scrim which draws a flat color
@@ -286,6 +288,7 @@
anim.addUpdateListener((v) -> invalidate(invalidateRegion));
getOverlay().add(drawable);
anim.start();
+ return true;
}
return value;
}
@@ -325,7 +328,7 @@
if (enabled) {
stateManager.addStateListener(this);
- handleStateChangedComplete(mLauncher.getStateManager().getState());
+ handleStateChangedComplete(stateManager.getState());
} else {
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
@@ -437,7 +440,24 @@
} else if (action == WALLPAPERS) {
return OptionsPopupView.startWallpaperPicker(ScrimView.this);
} else if (action == WIDGETS) {
- return OptionsPopupView.onWidgetsClicked(ScrimView.this);
+ int originalImportanceForAccessibility = getImportantForAccessibility();
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+ WidgetsFullSheet widgetsFullSheet = OptionsPopupView.openWidgets(mLauncher);
+ if (widgetsFullSheet == null) {
+ setImportantForAccessibility(originalImportanceForAccessibility);
+ return false;
+ }
+ widgetsFullSheet.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View view) {}
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ setImportantForAccessibility(originalImportanceForAccessibility);
+ widgetsFullSheet.removeOnAttachStateChangeListener(this);
+ }
+ });
+ return true;
} else if (action == SETTINGS) {
return OptionsPopupView.startSettings(ScrimView.this);
}
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index dce839f..f3fd7ca 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -44,6 +44,7 @@
import com.android.launcher3.StylusEventHelper;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
@@ -96,7 +97,7 @@
setBackgroundResource(R.drawable.widget_internal_focus_bg);
if (Utilities.ATLEAST_OREO) {
- setExecutor(Utilities.THREAD_POOL_EXECUTOR);
+ setExecutor(Executors.THREAD_POOL_EXECUTOR);
}
if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) {
setOnLightBackground(true);
@@ -332,12 +333,7 @@
if (shouldRegisterAutoAdvance != mIsAutoAdvanceRegistered) {
mIsAutoAdvanceRegistered = shouldRegisterAutoAdvance;
if (mAutoAdvanceRunnable == null) {
- mAutoAdvanceRunnable = new Runnable() {
- @Override
- public void run() {
- runAutoAdvance();
- }
- };
+ mAutoAdvanceRunnable = this::runAutoAdvance;
}
handler.removeCallbacks(mAutoAdvanceRunnable);
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index dc4af8c..6944879 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -37,6 +37,7 @@
import com.android.launcher3.StylusEventHelper;
import com.android.launcher3.WidgetPreviewLoader;
import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.model.WidgetItem;
/**
@@ -80,6 +81,7 @@
private Bitmap mDeferredBitmap;
protected final BaseActivity mActivity;
+ protected DeviceProfile mDeviceProfile;
public WidgetCell(Context context) {
this(context, null);
@@ -93,6 +95,7 @@
super(context, attrs, defStyle);
mActivity = BaseActivity.fromContext(context);
+ mDeviceProfile = mActivity.getDeviceProfile();
mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
setContainerWidth();
@@ -102,8 +105,7 @@
}
private void setContainerWidth() {
- DeviceProfile profile = mActivity.getDeviceProfile();
- mCellSize = (int) (profile.cellWidthPx * WIDTH_SCALE);
+ mCellSize = (int) (mDeviceProfile.allAppsCellWidthPx * WIDTH_SCALE);
mPresetPreviewSize = (int) (mCellSize * PREVIEW_SCALE);
}
@@ -180,8 +182,10 @@
return;
}
if (bitmap != null) {
- mWidgetImage.setBitmap(bitmap, DrawableFactory.INSTANCE.get(getContext())
- .getBadgeForUser(mItem.user, getContext()));
+ mWidgetImage.setBitmap(bitmap,
+ DrawableFactory.INSTANCE.get(getContext()).getBadgeForUser(mItem.user,
+ getContext(), BaseIconFactory.getBadgeSizeForIconSize(
+ mDeviceProfile.allAppsIconSizePx)));
if (mAnimatePreview) {
mWidgetImage.setAlpha(0f);
ViewPropertyAnimator anim = mWidgetImage.animate();
diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
new file mode 100644
index 0000000..f20c83d
--- /dev/null
+++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2019 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.widget.custom;
+
+import static com.android.launcher3.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Process;
+import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.systemui.plugins.CustomWidgetPlugin;
+import com.android.systemui.plugins.PluginListener;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * CustomWidgetManager handles custom widgets implemented as a plugin.
+ */
+public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin> {
+
+ public static final MainThreadInitializedObject<CustomWidgetManager> INSTANCE =
+ new MainThreadInitializedObject<>(CustomWidgetManager::new);
+
+ /**
+ * auto provider Id is an ever-increasing number that serves as the providerId whenever a new
+ * custom widget has been connected.
+ */
+ private int mAutoProviderId = 0;
+ private final SparseArray<CustomWidgetPlugin> mPlugins;
+ private final SparseArray<WeakReference<Context>> mContexts;
+ private final List<CustomAppWidgetProviderInfo> mCustomWidgets;
+ private final SparseArray<ComponentName> mWidgetsIdMap;
+ private Consumer<PackageUserKey> mWidgetRefreshCallback;
+
+ private CustomWidgetManager(Context context) {
+ mPlugins = new SparseArray<>();
+ mContexts = new SparseArray<>();
+ mCustomWidgets = new ArrayList<>();
+ mWidgetsIdMap = new SparseArray<>();
+ PluginManagerWrapper.INSTANCE.get(context)
+ .addPluginListener(this, CustomWidgetPlugin.class, true);
+ }
+
+ @Override
+ public void onPluginConnected(CustomWidgetPlugin plugin, Context context) {
+ mPlugins.put(mAutoProviderId, plugin);
+ mContexts.put(mAutoProviderId, new WeakReference<>(context));
+ List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(context)
+ .getInstalledProvidersForProfile(Process.myUserHandle());
+ if (providers.isEmpty()) return;
+ Parcel parcel = Parcel.obtain();
+ providers.get(0).writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ CustomAppWidgetProviderInfo info = newInfo(mAutoProviderId, plugin, parcel, context);
+ parcel.recycle();
+ mCustomWidgets.add(info);
+ mWidgetsIdMap.put(mAutoProviderId, info.provider);
+ mWidgetRefreshCallback.accept(null);
+ mAutoProviderId++;
+ }
+
+ @Override
+ public void onPluginDisconnected(CustomWidgetPlugin plugin) {
+ int providerId = findProviderId(plugin);
+ if (providerId == -1) return;
+ mPlugins.remove(providerId);
+ mContexts.remove(providerId);
+ mCustomWidgets.remove(getWidgetProvider(providerId));
+ mWidgetsIdMap.remove(providerId);
+ }
+
+ /**
+ * Inject a callback function to refresh the widgets.
+ */
+ public void setWidgetRefreshCallback(Consumer<PackageUserKey> cb) {
+ mWidgetRefreshCallback = cb;
+ }
+
+ /**
+ * Callback method to inform a plugin it's corresponding widget has been created.
+ */
+ public void onViewCreated(LauncherAppWidgetHostView view) {
+ CustomAppWidgetProviderInfo info = (CustomAppWidgetProviderInfo) view.getAppWidgetInfo();
+ CustomWidgetPlugin plugin = mPlugins.get(info.providerId);
+ WeakReference<Context> context = mContexts.get(info.providerId);
+ if (plugin == null) return;
+ plugin.onViewCreated(context == null ? null : context.get(), view);
+ }
+
+ /**
+ * Returns the list of custom widgets.
+ */
+ @NonNull
+ public List<CustomAppWidgetProviderInfo> getCustomWidgets() {
+ return mCustomWidgets;
+ }
+
+ /**
+ * Returns the widget id for a specific provider.
+ */
+ public int getWidgetIdForCustomProvider(@NonNull ComponentName provider) {
+ int index = mWidgetsIdMap.indexOfValue(provider);
+ if (index >= 0) {
+ return LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - mWidgetsIdMap.keyAt(index);
+ } else {
+ return AppWidgetManager.INVALID_APPWIDGET_ID;
+ }
+ }
+
+ /**
+ * Returns the widget provider in respect to given widget id.
+ */
+ @Nullable
+ public LauncherAppWidgetProviderInfo getWidgetProvider(int widgetId) {
+ ComponentName cn = mWidgetsIdMap.get(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - widgetId);
+ for (LauncherAppWidgetProviderInfo info : mCustomWidgets) {
+ if (info.provider.equals(cn)) return info;
+ }
+ return null;
+ }
+
+ private static CustomAppWidgetProviderInfo newInfo(int providerId, CustomWidgetPlugin plugin,
+ Parcel parcel, Context context) {
+ CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo(
+ parcel, false, providerId);
+ info.provider = new ComponentName(
+ context.getPackageName(), CLS_CUSTOM_WIDGET_PREFIX + providerId);
+
+ info.label = plugin.getLabel(context);
+ info.resizeMode = plugin.getResizeMode(context);
+
+ info.spanX = plugin.getSpanX(context);
+ info.spanY = plugin.getSpanY(context);
+ info.minSpanX = plugin.getMinSpanX(context);
+ info.minSpanY = plugin.getMinSpanY(context);
+ return info;
+ }
+
+ private int findProviderId(CustomWidgetPlugin plugin) {
+ for (int i = 0; i < mPlugins.size(); i++) {
+ int providerId = mPlugins.keyAt(i);
+ if (mPlugins.get(providerId) == plugin) {
+ return providerId;
+ }
+ }
+ return -1;
+ }
+}
diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetParser.java b/src/com/android/launcher3/widget/custom/CustomWidgetParser.java
deleted file mode 100644
index 00720c4..0000000
--- a/src/com/android/launcher3/widget/custom/CustomWidgetParser.java
+++ /dev/null
@@ -1,142 +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.widget.custom;
-
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.os.Parcel;
-import android.os.Process;
-import android.util.SparseArray;
-import android.util.Xml;
-
-import com.android.launcher3.LauncherAppWidgetInfo;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.android.launcher3.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX;
-
-/**
- * Utility class to parse {@ink CustomAppWidgetProviderInfo} definitions from xml
- */
-public class CustomWidgetParser {
-
- private static List<LauncherAppWidgetProviderInfo> sCustomWidgets;
- private static SparseArray<ComponentName> sWidgetsIdMap;
-
- public static List<LauncherAppWidgetProviderInfo> getCustomWidgets(Context context) {
- if (sCustomWidgets == null) {
- // Synchronization not needed as it it safe to load multiple times
- parseCustomWidgets(context);
- }
-
- return sCustomWidgets;
- }
-
- public static int getWidgetIdForCustomProvider(Context context, ComponentName provider) {
- if (sWidgetsIdMap == null) {
- parseCustomWidgets(context);
- }
- int index = sWidgetsIdMap.indexOfValue(provider);
- if (index >= 0) {
- return LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - sWidgetsIdMap.keyAt(index);
- } else {
- return AppWidgetManager.INVALID_APPWIDGET_ID;
- }
- }
-
- public static LauncherAppWidgetProviderInfo getWidgetProvider(Context context, int widgetId) {
- if (sWidgetsIdMap == null || sCustomWidgets == null) {
- parseCustomWidgets(context);
- }
- ComponentName cn = sWidgetsIdMap.get(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - widgetId);
- for (LauncherAppWidgetProviderInfo info : sCustomWidgets) {
- if (info.provider.equals(cn)) {
- return info;
- }
- }
- return null;
- }
-
- private static void parseCustomWidgets(Context context) {
- ArrayList<LauncherAppWidgetProviderInfo> widgets = new ArrayList<>();
- SparseArray<ComponentName> idMap = new SparseArray<>();
-
- List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(context)
- .getInstalledProvidersForProfile(Process.myUserHandle());
- if (providers.isEmpty()) {
- sCustomWidgets = widgets;
- sWidgetsIdMap = idMap;
- return;
- }
-
- Parcel parcel = Parcel.obtain();
- providers.get(0).writeToParcel(parcel, 0);
-
- try (XmlResourceParser parser = context.getResources().getXml(R.xml.custom_widgets)) {
- final int depth = parser.getDepth();
- int type;
-
- while (((type = parser.next()) != XmlPullParser.END_TAG ||
- parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
- if ((type == XmlPullParser.START_TAG) && "widget".equals(parser.getName())) {
- TypedArray a = context.obtainStyledAttributes(
- Xml.asAttributeSet(parser), R.styleable.CustomAppWidgetProviderInfo);
-
- parcel.setDataPosition(0);
- CustomAppWidgetProviderInfo info = newInfo(a, parcel, context);
- widgets.add(info);
- a.recycle();
-
- idMap.put(info.providerId, info.provider);
- }
- }
- } catch (IOException | XmlPullParserException e) {
- throw new RuntimeException(e);
- }
- parcel.recycle();
- sCustomWidgets = widgets;
- sWidgetsIdMap = idMap;
- }
-
- private static CustomAppWidgetProviderInfo newInfo(TypedArray a, Parcel parcel, Context context) {
- int providerId = a.getInt(R.styleable.CustomAppWidgetProviderInfo_providerId, 0);
- CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo(parcel, false, providerId);
- info.provider = new ComponentName(context.getPackageName(), CLS_CUSTOM_WIDGET_PREFIX + providerId);
-
- info.label = a.getString(R.styleable.CustomAppWidgetProviderInfo_android_label);
- info.initialLayout = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_initialLayout, 0);
- info.icon = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_icon, 0);
- info.previewImage = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_previewImage, 0);
- info.resizeMode = a.getInt(R.styleable.CustomAppWidgetProviderInfo_android_resizeMode, 0);
-
- info.spanX = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numColumns, 1);
- info.spanY = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numRows, 1);
- info.minSpanX = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numMinColumns, 1);
- info.minSpanY = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numMinRows, 1);
- return info;
- }
-}
diff --git a/src_plugins/com/android/systemui/plugins/AppLaunchEventsPlugin.java b/src_plugins/com/android/systemui/plugins/AppLaunchEventsPlugin.java
new file mode 100644
index 0000000..15a0ffa
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/AppLaunchEventsPlugin.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 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.systemui.plugins;
+
+import android.content.ComponentName;
+import android.os.UserHandle;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Plugin interface which sends app launch events.
+ */
+@ProvidesInterface(action = AppLaunchEventsPlugin.ACTION, version = AppLaunchEventsPlugin.VERSION)
+public interface AppLaunchEventsPlugin extends Plugin {
+ String ACTION = "com.android.systemui.action.PLUGIN_APP_EVENTS";
+ int VERSION = 1;
+
+ /**
+ * Receives onStartShortcut event from
+ * {@link com.android.launcher3.appprediction.PredictionAppTracker}.
+ */
+ void onStartShortcut(String packageName, String shortcutId, UserHandle user, String container);
+
+ /**
+ * Receives onStartApp event from
+ * {@link com.android.launcher3.appprediction.PredictionAppTracker}.
+ */
+ void onStartApp(ComponentName componentName, UserHandle user, String container);
+
+ /**
+ * Receives onDismissApp event from
+ * {@link com.android.launcher3.appprediction.PredictionAppTracker}.
+ */
+ void onDismissApp(ComponentName componentName, UserHandle user, String container);
+
+ /**
+ * Receives onReturnedToHome event from
+ * {@link com.android.launcher3.appprediction.PredictionAppTracker}.
+ */
+ void onReturnedToHome();
+}
diff --git a/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java b/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java
new file mode 100644
index 0000000..47aa94b
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 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.systemui.plugins;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.Context;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this plugin interface to add a custom widget.
+ */
+@ProvidesInterface(action = CustomWidgetPlugin.ACTION, version = CustomWidgetPlugin.VERSION)
+public interface CustomWidgetPlugin extends Plugin {
+
+ String ACTION = "com.android.systemui.action.PLUGIN_CUSTOM_WIDGET";
+ int VERSION = 1;
+
+ /**
+ * The label to display to the user in the AppWidget picker.
+ */
+ String getLabel(Context context);
+
+ /**
+ * The default width of the widget when added to a host, in dp. The widget will get
+ * at least this width, and will often be given more, depending on the host.
+ */
+ int getSpanX(Context context);
+
+ /**
+ * The default height of the widget when added to a host, in dp. The widget will get
+ * at least this height, and will often be given more, depending on the host.
+ */
+ int getSpanY(Context context);
+
+ /**
+ * Minimum width (in dp) which the widget can be resized to. This field has no effect if it
+ * is greater than minWidth or if horizontal resizing isn't enabled.
+ */
+ int getMinSpanX(Context context);
+
+ /**
+ * Minimum height (in dp) which the widget can be resized to. This field has no effect if it
+ * is greater than minHeight or if vertical resizing isn't enabled.
+ */
+ int getMinSpanY(Context context);
+
+ /**
+ * The rules by which a widget can be resized.
+ */
+ int getResizeMode(Context context);
+
+ /**
+ * Notify the plugin that container of the widget has been rendered, where the custom widget
+ * can be attached to.
+ */
+ void onViewCreated(Context context, AppWidgetHostView parent);
+}
diff --git a/src_plugins/com/android/systemui/plugins/HotseatPlugin.java b/src_plugins/com/android/systemui/plugins/HotseatPlugin.java
new file mode 100644
index 0000000..1264e0d
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/HotseatPlugin.java
@@ -0,0 +1,20 @@
+package com.android.systemui.plugins;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this plugin interface to add a sub-view in the Hotseat.
+ */
+@ProvidesInterface(action = HotseatPlugin.ACTION, version = HotseatPlugin.VERSION)
+public interface HotseatPlugin extends Plugin {
+ String ACTION = "com.android.systemui.action.PLUGIN_HOTSEAT";
+ int VERSION = 1;
+
+ /**
+ * Creates a plugin view which will be added to the Hotseat.
+ */
+ View createView(ViewGroup parent);
+}
diff --git a/src_plugins/com/android/systemui/plugins/OverviewScreenshotActions.java b/src_plugins/com/android/systemui/plugins/OverviewScreenshotActions.java
new file mode 100644
index 0000000..8d9c0f4
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/OverviewScreenshotActions.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 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.systemui.plugins;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this interface to add action buttons for overview screenshots, e.g. share, edit etc.
+ */
+@ProvidesInterface(
+ action = OverviewScreenshotActions.ACTION, version = OverviewScreenshotActions.VERSION)
+public interface OverviewScreenshotActions extends Plugin {
+ String ACTION = "com.android.systemui.action.PLUGIN_OVERVIEW_SCREENSHOT_ACTIONS";
+ int VERSION = 1;
+
+ /**
+ * Setup the actions for the screenshot, including edit, save, etc.
+ * @param parent The parent view to add buttons on.
+ * @param screenshot The screenshot we will do actions on.
+ * @param activity THe host activity.
+ */
+ void setupActions(ViewGroup parent, Bitmap screenshot, Activity activity);
+}
diff --git a/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java b/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java
new file mode 100644
index 0000000..0ebea3d
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 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.systemui.plugins;
+
+import android.app.Activity;
+import android.content.Context;
+import android.widget.FrameLayout;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this interface to allow extra card on recents overview.
+ */
+@ProvidesInterface(action = RecentsExtraCard.ACTION, version = RecentsExtraCard.VERSION)
+public interface RecentsExtraCard extends Plugin {
+
+ String ACTION = "com.android.systemui.action.PLUGIN_RECENTS_EXTRA_CARD";
+ int VERSION = 1;
+
+ /**
+ * Sets up the recents overview extra card and fills in data.
+ *
+ * @param context Plugin context
+ * @param frameLayout PlaceholderView
+ * @param activity Recents activity to hold extra view
+ */
+ void setupView(Context context, FrameLayout frameLayout, Activity activity);
+}
diff --git a/src_plugins/com/android/systemui/plugins/UserEventPlugin.java b/src_plugins/com/android/systemui/plugins/UserEventPlugin.java
new file mode 100644
index 0000000..0e3664a
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/UserEventPlugin.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 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.systemui.plugins;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this plugin interface to access user event log on the device for prototype purpose.
+ * NOTE: plugin is for internal prototype only and is not visible in production environment.
+ */
+@ProvidesInterface(action = UserEventPlugin.ACTION, version = UserEventPlugin.VERSION)
+public interface UserEventPlugin extends Plugin {
+ String ACTION = "com.android.launcher3.action.PLUGIN_USER_EVENT_LOG";
+ int VERSION = 1;
+
+ /**
+ * Callback to be triggered whenever an user event occurs.
+ */
+ void onUserEvent(Object event);
+}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
index 1710aef..789bfd8 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
@@ -16,9 +16,8 @@
package com.android.launcher3.model;
-import com.android.launcher3.AllAppsList;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.widget.WidgetListRowEntry;
diff --git a/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java
index 6b6f70d..57f4164 100644
--- a/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -27,9 +27,7 @@
import android.os.UserHandle;
import android.util.Log;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.WorkspaceItemInfo;
+import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
@@ -45,42 +43,27 @@
| ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_PINNED;
private static DeepShortcutManager sInstance;
- private static final Object sInstanceLock = new Object();
public static DeepShortcutManager getInstance(Context context) {
- synchronized (sInstanceLock) {
- if (sInstance == null) {
- sInstance = new DeepShortcutManager(context.getApplicationContext());
- }
- return sInstance;
+ if (sInstance == null) {
+ sInstance = new DeepShortcutManager(context.getApplicationContext());
}
+ return sInstance;
}
private final LauncherApps mLauncherApps;
- private boolean mWasLastCallSuccess;
private DeepShortcutManager(Context context) {
mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
}
- public static boolean supportsShortcuts(ItemInfo info) {
- boolean isItemPromise = info instanceof WorkspaceItemInfo
- && ((WorkspaceItemInfo) info).hasPromiseIconUi();
- return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
- && !info.isDisabled() && !isItemPromise;
- }
-
- public boolean wasLastCallSuccess() {
- return mWasLastCallSuccess;
- }
-
/**
* Queries for the shortcuts with the package name and provided ids.
*
* This method is intended to get the full details for shortcuts when they are added or updated,
* because we only get "key" fields in onShortcutsChanged().
*/
- public List<ShortcutInfo> queryForFullDetails(String packageName,
+ public QueryResult queryForFullDetails(String packageName,
List<String> shortcutIds, UserHandle user) {
return query(FLAG_GET_ALL, packageName, null, shortcutIds, user);
}
@@ -89,8 +72,9 @@
* Gets all the manifest and dynamic shortcuts associated with the given package and user,
* to be displayed in the shortcuts container on long press.
*/
- public List<ShortcutInfo> queryForShortcutsContainer(ComponentName activity,
+ public QueryResult queryForShortcutsContainer(@Nullable ComponentName activity,
UserHandle user) {
+ if (activity == null) return QueryResult.FAILURE;
return query(ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC,
activity.getPackageName(), activity, null, user);
}
@@ -107,10 +91,8 @@
pinnedIds.remove(id);
try {
mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
- mWasLastCallSuccess = true;
} catch (SecurityException|IllegalStateException e) {
Log.w(TAG, "Failed to unpin shortcut", e);
- mWasLastCallSuccess = false;
}
}
@@ -126,10 +108,8 @@
pinnedIds.add(id);
try {
mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
- mWasLastCallSuccess = true;
} catch (SecurityException|IllegalStateException e) {
Log.w(TAG, "Failed to pin shortcut", e);
- mWasLastCallSuccess = false;
}
}
@@ -138,23 +118,18 @@
try {
mLauncherApps.startShortcut(packageName, id, sourceBounds,
startActivityOptions, user);
- mWasLastCallSuccess = true;
} catch (SecurityException|IllegalStateException e) {
Log.e(TAG, "Failed to start shortcut", e);
- mWasLastCallSuccess = false;
}
}
public Drawable getShortcutIconDrawable(ShortcutInfo shortcutInfo, int density) {
try {
- Drawable icon = mLauncherApps.getShortcutIconDrawable(shortcutInfo, density);
- mWasLastCallSuccess = true;
- return icon;
+ return mLauncherApps.getShortcutIconDrawable(shortcutInfo, density);
} catch (SecurityException|IllegalStateException e) {
Log.e(TAG, "Failed to get shortcut icon", e);
- mWasLastCallSuccess = false;
+ return null;
}
- return null;
}
/**
@@ -162,20 +137,20 @@
*
* If packageName is null, returns all pinned shortcuts regardless of package.
*/
- public List<ShortcutInfo> queryForPinnedShortcuts(String packageName, UserHandle user) {
+ public QueryResult queryForPinnedShortcuts(String packageName, UserHandle user) {
return queryForPinnedShortcuts(packageName, null, user);
}
- public List<ShortcutInfo> queryForPinnedShortcuts(String packageName,
- List<String> shortcutIds, UserHandle user) {
+ public QueryResult queryForPinnedShortcuts(String packageName, List<String> shortcutIds,
+ UserHandle user) {
return query(ShortcutQuery.FLAG_MATCH_PINNED, packageName, null, shortcutIds, user);
}
- public List<ShortcutInfo> queryForAllShortcuts(UserHandle user) {
+ public QueryResult queryForAllShortcuts(UserHandle user) {
return query(FLAG_GET_ALL, null, null, null, user);
}
- private List<String> extractIds(List<ShortcutInfo> shortcuts) {
+ private static List<String> extractIds(List<ShortcutInfo> shortcuts) {
List<String> shortcutIds = new ArrayList<>(shortcuts.size());
for (ShortcutInfo shortcut : shortcuts) {
shortcutIds.add(shortcut.getId());
@@ -189,8 +164,8 @@
*
* TODO: Use the cache to optimize this so we don't make an RPC every time.
*/
- private List<ShortcutInfo> query(int flags, String packageName,
- ComponentName activity, List<String> shortcutIds, UserHandle user) {
+ private QueryResult query(int flags, String packageName, ComponentName activity,
+ List<String> shortcutIds, UserHandle user) {
ShortcutQuery q = new ShortcutQuery();
q.setQueryFlags(flags);
if (packageName != null) {
@@ -198,18 +173,12 @@
q.setActivity(activity);
q.setShortcutIds(shortcutIds);
}
- List<ShortcutInfo> shortcutInfos = null;
try {
- shortcutInfos = mLauncherApps.getShortcuts(q, user);
- mWasLastCallSuccess = true;
+ return new QueryResult(mLauncherApps.getShortcuts(q, user));
} catch (SecurityException|IllegalStateException e) {
Log.e(TAG, "Failed to query for shortcuts", e);
- mWasLastCallSuccess = false;
+ return QueryResult.FAILURE;
}
- if (shortcutInfos == null) {
- return Collections.EMPTY_LIST;
- }
- return shortcutInfos;
}
public boolean hasHostPermission() {
@@ -220,4 +189,25 @@
}
return false;
}
+
+ public static class QueryResult extends ArrayList<ShortcutInfo> {
+
+ static QueryResult FAILURE = new QueryResult();
+
+ private final boolean mWasSuccess;
+
+ QueryResult(List<ShortcutInfo> result) {
+ super(result == null ? Collections.emptyList() : result);
+ mWasSuccess = true;
+ }
+
+ QueryResult() {
+ mWasSuccess = false;
+ }
+
+
+ public boolean wasSuccess() {
+ return mWasSuccess;
+ }
+ }
}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
index e9dc800..23f21a1 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
@@ -10,7 +10,7 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.AnimationComponents;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
/**
@@ -21,7 +21,7 @@
private MotionEvent mTouchDownEvent;
public AllAppsSwipeController(Launcher l) {
- super(l, SwipeDetector.VERTICAL);
+ super(l, SingleAxisSwipeDetector.VERTICAL);
}
@Override
@@ -57,7 +57,7 @@
}
@Override
- protected int getLogContainerTypeForNormalState() {
+ protected int getLogContainerTypeForNormalState(MotionEvent ev) {
return mLauncher.getDragLayer().isEventOverView(mLauncher.getHotseat(), mTouchDownEvent) ?
ContainerType.HOTSEAT : ContainerType.WORKSPACE;
}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java b/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java
new file mode 100644
index 0000000..60f12d8
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 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.uioverrides;
+
+import android.content.Context;
+import com.android.launcher3.config.BaseFlags.BaseTogglableFlag;
+
+public class TogglableFlag extends BaseTogglableFlag {
+
+ public TogglableFlag(String key, boolean defaultValue, String description) {
+ super(key, defaultValue, description);
+ }
+
+ @Override
+ public boolean getOverridenDefaultValue(boolean value) {
+ return value;
+ }
+
+ @Override
+ public void addChangeListener(Context context, Runnable r) { }
+}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index 5cc64dc..f2b5ed2 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -17,9 +17,11 @@
package com.android.launcher3.uioverrides;
import android.app.Activity;
+import android.app.Person;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.pm.ShortcutInfo;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -27,6 +29,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState.ScaleAndTranslation;
import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.Utilities;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.util.TouchController;
@@ -95,4 +98,9 @@
public static void clearSwipeSharedState(boolean finishAnimation) {}
+ public static Person[] getPersons(ShortcutInfo si) {
+ return Utilities.EMPTY_PERSON_ARRAY;
+ }
+
+ public static void closeSystemWindows() {}
}
diff --git a/tests/Android.mk b/tests/Android.mk
index 0c41241..02ead4e 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -27,7 +27,7 @@
ifneq (,$(wildcard frameworks/base))
else
- LOCAL_STATIC_JAVA_LIBRARIES += libSharedSystemUI
+ LOCAL_STATIC_JAVA_LIBRARIES += SystemUISharedLib
LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
../src/com/android/launcher3/ResourceUtils.java \
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 61c7306..ffa90b9 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -20,6 +20,9 @@
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+ <uses-permission android:name="android.permission.READ_LOGS"/>
+
<application android:debuggable="true">
<uses-library android:name="android.test.runner"/>
@@ -59,6 +62,12 @@
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter>
</activity>
+ <activity android:name="com.android.launcher3.testcomponent.CustomShortcutConfigActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.CREATE_SHORTCUT" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
<activity
android:name="com.android.launcher3.testcomponent.RequestPinItemActivity"
android:icon="@drawable/test_drawable_pin_item"
diff --git a/tests/OWNERS b/tests/OWNERS
index 046d871..02e8ebc 100644
--- a/tests/OWNERS
+++ b/tests/OWNERS
@@ -1 +1,4 @@
vadimt@google.com
+sunnygoyal@google.com
+winsonc@google.com
+hyunyoungs@google.com
diff --git a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
new file mode 100644
index 0000000..efbd9c9
--- /dev/null
+++ b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2019 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.compat;
+
+import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.text.TextUtils;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.UUID;
+
+
+/**
+ * Test to verify promise icon flow.
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class PromiseIconUiTest extends AbstractLauncherUiTest {
+
+ private int mSessionId = -1;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mDevice.pressHome();
+ waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
+ waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
+ mSessionId = -1;
+ }
+
+ @After
+ public void tearDown() {
+ if (mSessionId > -1) {
+ mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
+ }
+ }
+
+ /**
+ * Create a session and return the id.
+ */
+ private int createSession(String label, Bitmap icon) throws Throwable {
+ SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+ params.setAppPackageName("test.promise.app");
+ params.setAppLabel(label);
+ params.setAppIcon(icon);
+ params.setInstallReason(PackageManager.INSTALL_REASON_USER);
+ return mTargetContext.getPackageManager().getPackageInstaller().createSession(params);
+ }
+
+ @Test
+ public void testPromiseIcon_addedFromEligibleSession() throws Throwable {
+ final String appLabel = "Test Promise App " + UUID.randomUUID().toString();
+ final Workspace.ItemOperator findPromiseApp = (info, view) ->
+ info != null && TextUtils.equals(info.title, appLabel);
+
+ // Create and add test session
+ mSessionId = createSession(appLabel, Bitmap.createBitmap(100, 100, Bitmap.Config.ALPHA_8));
+
+ // Verify promise icon is added
+ waitForLauncherCondition("Test Promise App not found on workspace", launcher ->
+ launcher.getWorkspace().getFirstMatch(findPromiseApp) != null);
+
+ // Remove session
+ mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
+ mSessionId = -1;
+
+ // Verify promise icon is removed
+ waitForLauncherCondition("Test Promise App not removed from workspace", launcher ->
+ launcher.getWorkspace().getFirstMatch(findPromiseApp) == null);
+ }
+
+ @Test
+ public void testPromiseIcon_notAddedFromIneligibleSession() throws Throwable {
+ final String appLabel = "Test Promise App " + UUID.randomUUID().toString();
+ final Workspace.ItemOperator findPromiseApp = (info, view) ->
+ info != null && TextUtils.equals(info.title, appLabel);
+
+ // Create and add test session without icon or label
+ mSessionId = createSession(null, null);
+
+ // Sleep for duration of animation if a view was to be added + some buffer time.
+ Thread.sleep(Launcher.NEW_APPS_PAGE_MOVE_DELAY + Launcher.NEW_APPS_ANIMATION_DELAY + 500);
+
+ // Verify promise icon is not added
+ waitForLauncherCondition("Test Promise App not found on workspace", launcher ->
+ launcher.getWorkspace().getFirstMatch(findPromiseApp) == null);
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 7d60ad6..64df8e0 100644
--- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -15,9 +15,9 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.Utilities;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.util.PackageManagerHelper;
import org.junit.Before;
import org.junit.Test;
@@ -115,7 +115,7 @@
WorkspaceItemInfo info = mLoaderCursor.getAppShortcutInfo(
new Intent().setComponent(cn), false /* allowMissingTarget */, true);
assertNotNull(info);
- assertTrue(Utilities.isLauncherAppTarget(info.intent));
+ assertTrue(PackageManagerHelper.isLauncherAppTarget(info.intent));
}
@Test
@@ -127,7 +127,7 @@
WorkspaceItemInfo info = mLoaderCursor.getAppShortcutInfo(
new Intent().setComponent(cn), true /* allowMissingTarget */, true);
assertNotNull(info);
- assertTrue(Utilities.isLauncherAppTarget(info.intent));
+ assertTrue(PackageManagerHelper.isLauncherAppTarget(info.intent));
}
@Test
diff --git a/tests/src/com/android/launcher3/testcomponent/CustomShortcutConfigActivity.java b/tests/src/com/android/launcher3/testcomponent/CustomShortcutConfigActivity.java
new file mode 100644
index 0000000..b673faa
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/CustomShortcutConfigActivity.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 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.testcomponent;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+
+import com.android.launcher3.R;
+
+import java.util.UUID;
+
+/**
+ * A custom shortcut is a 1x1 widget that launches a specific intent when user tap on it.
+ * Custom shortcuts are replaced by deep shortcuts after api 25.
+ */
+public class CustomShortcutConfigActivity extends BaseTestingActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent launchIntent = new Intent(this, BaseTestingActivity.class)
+ .setAction("com.android.launcher3.intent.action.test_shortcut");
+ Intent shortcutIntent = createShortcutResultIntent(
+ this, UUID.randomUUID().toString(), "Shortcut",
+ R.drawable.ic_widget, launchIntent);
+ setResult(RESULT_OK, shortcutIntent);
+ finish();
+ }
+
+ private static Intent createShortcutResultIntent(
+ Context context, String uniqueId, String name, int iconId, Intent launchIntent) {
+ ShortcutInfo shortcutInfo =
+ createShortcutInfo(context, uniqueId, name, iconId, launchIntent);
+ ShortcutManager sm = context.getSystemService(ShortcutManager.class);
+ return sm.createShortcutResultIntent(shortcutInfo);
+ }
+
+ private static ShortcutInfo createShortcutInfo(
+ Context context, String uniqueId, String name, int iconId, Intent launchIntent) {
+ return new ShortcutInfo.Builder(context, uniqueId)
+ .setShortLabel(name)
+ .setLongLabel(name)
+ .setIcon(Icon.createWithResource(context, iconId))
+ .setIntent(launchIntent)
+ .build();
+ }
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
index fa23b8d..4246096 100644
--- a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
+++ b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
@@ -32,13 +32,14 @@
import android.os.ParcelFileDescriptor;
import android.util.Base64;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.launcher3.tapl.TestHelpers;
+
import java.io.File;
import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
-import androidx.test.InstrumentationRegistry;
-
/**
* Content provider to receive commands from tests
*/
@@ -47,6 +48,7 @@
public static final String ENABLE_TEST_LAUNCHER = "enable-test-launcher";
public static final String DISABLE_TEST_LAUNCHER = "disable-test-launcher";
public static final String KILL_PROCESS = "kill-process";
+ public static final String GET_SYSTEM_HEALTH_MESSAGE = "get-system-health-message";
@Override
public boolean onCreate() {
@@ -99,6 +101,13 @@
killBackgroundProcesses(arg);
return null;
}
+
+ case GET_SYSTEM_HEALTH_MESSAGE: {
+ final Bundle response = new Bundle();
+ response.putString("result",
+ TestHelpers.getSystemHealthMessage(getContext(), Long.parseLong(arg)));
+ return response;
+ }
}
return super.call(method, arg, extras);
}
@@ -122,7 +131,8 @@
// Create an empty file so that we can pass its descriptor
try {
file.createNewFile();
- } catch (IOException e) { }
+ } catch (IOException e) {
+ }
}
return ParcelFileDescriptor.open(file, MODE_READ_WRITE);
diff --git a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
similarity index 72%
rename from tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
rename to tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
index e042357..5174e4d 100644
--- a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
+++ b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
@@ -15,6 +15,12 @@
*/
package com.android.launcher3.touch;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
+
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyFloat;
import static org.mockito.Matchers.anyObject;
@@ -25,6 +31,10 @@
import android.util.Log;
import android.view.ViewConfiguration;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.launcher3.testcomponent.TouchEventGenerator;
import org.junit.Before;
@@ -33,25 +43,21 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class SwipeDetectorTest {
+public class SingleAxisSwipeDetectorTest {
- private static final String TAG = SwipeDetectorTest.class.getSimpleName();
+ private static final String TAG = SingleAxisSwipeDetectorTest.class.getSimpleName();
public static void L(String s, Object... parts) {
Log.d(TAG, (parts.length == 0) ? s : String.format(s, parts));
}
private TouchEventGenerator mGenerator;
- private SwipeDetector mDetector;
+ private SingleAxisSwipeDetector mDetector;
private int mTouchSlop;
@Mock
- private SwipeDetector.Listener mMockListener;
+ private SingleAxisSwipeDetector.Listener mMockListener;
@Mock
private ViewConfiguration mMockConfig;
@@ -65,8 +71,8 @@
doReturn(orgConfig.getScaledMaximumFlingVelocity()).when(mMockConfig)
.getScaledMaximumFlingVelocity();
- mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL, false);
- mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
+ mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, VERTICAL, false);
+ mDetector.setDetectableScrollConditions(DIRECTION_BOTH, false);
mTouchSlop = orgConfig.getScaledTouchSlop();
doReturn(mTouchSlop).when(mMockConfig).getScaledTouchSlop();
@@ -75,8 +81,8 @@
@Test
public void testDragStart_verticalPositive() {
- mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL, false);
- mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
+ mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, VERTICAL, false);
+ mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false);
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100, 100 - mTouchSlop);
// TODO: actually calculate the following parameters and do exact value checks.
@@ -85,8 +91,8 @@
@Test
public void testDragStart_verticalNegative() {
- mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL, false);
- mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_NEGATIVE, false);
+ mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, VERTICAL, false);
+ mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false);
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100, 100 + mTouchSlop);
// TODO: actually calculate the following parameters and do exact value checks.
@@ -103,8 +109,8 @@
@Test
public void testDragStart_horizontalPositive() {
- mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL, false);
- mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
+ mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, false);
+ mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false);
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100 + mTouchSlop, 100);
@@ -114,8 +120,8 @@
@Test
public void testDragStart_horizontalNegative() {
- mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL, false);
- mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_NEGATIVE, false);
+ mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, false);
+ mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false);
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100 - mTouchSlop, 100);
@@ -125,8 +131,8 @@
@Test
public void testDragStart_horizontalRtlPositive() {
- mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL, true);
- mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
+ mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, true);
+ mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false);
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100 - mTouchSlop, 100);
@@ -136,8 +142,8 @@
@Test
public void testDragStart_horizontalRtlNegative() {
- mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL, true);
- mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_NEGATIVE, false);
+ mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, true);
+ mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false);
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100 + mTouchSlop, 100);
@@ -160,6 +166,6 @@
mGenerator.move(0, 100, 100 + mTouchSlop * 2);
mGenerator.lift(0);
// TODO: actually calculate the following parameters and do exact value checks.
- verify(mMockListener).onDragEnd(anyFloat(), anyBoolean());
+ verify(mMockListener).onDragEnd(anyFloat());
}
}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 4a0ca5c..9a894b1 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -15,16 +15,16 @@
*/
package com.android.launcher3.ui;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
+import static com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import static java.lang.System.exit;
-import android.app.Instrumentation;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -39,9 +39,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
-import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
import com.android.launcher3.Launcher;
@@ -49,17 +47,21 @@
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.MainThreadExecutor;
-import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.tapl.TestHelpers;
+import com.android.launcher3.testcomponent.TestCommandReceiver;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.util.LooperExecutor;
+import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.FailureWatcher;
import com.android.launcher3.util.rule.LauncherActivityRule;
import com.android.launcher3.util.rule.ShellCommandRule;
+import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.After;
import org.junit.Before;
@@ -86,14 +88,12 @@
public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
- public static final long SHORT_UI_TIMEOUT = 300;
- public static final long DEFAULT_UI_TIMEOUT = 10000;
+ public static final long DEFAULT_UI_TIMEOUT = 60000; // b/136278866
private static final String TAG = "AbstractLauncherUiTest";
- protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
+ protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
- protected final LauncherInstrumentation mLauncher =
- new LauncherInstrumentation(getInstrumentation());
+ protected final LauncherInstrumentation mLauncher = new LauncherInstrumentation();
protected Context mTargetContext;
protected String mTargetPackage;
@@ -103,7 +103,17 @@
} catch (RemoteException e) {
throw new RuntimeException(e);
}
- if (TestHelpers.isInLauncherProcess()) Utilities.enableRunningInTestHarnessForTests();
+ if (TestHelpers.isInLauncherProcess()) {
+ Utilities.enableRunningInTestHarnessForTests();
+ mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand(
+ TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()).
+ getString("result"));
+ mLauncher.setOnSettledStateAction(
+ containerType -> executeOnLauncher(
+ launcher ->
+ checkLauncherIntegrity(launcher, containerType)));
+ }
+ mLauncher.enableDebugTracing();
}
protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
@@ -116,6 +126,23 @@
public ShellCommandRule mDisableHeadsUpNotification =
ShellCommandRule.disableHeadsUpNotification();
+ protected void clearPackageData(String pkg) throws IOException, InterruptedException {
+ final CountDownLatch count = new CountDownLatch(2);
+ final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ count.countDown();
+ }
+ };
+ mTargetContext.registerReceiver(broadcastReceiver,
+ PackageManagerHelper.getPackageFilter(pkg,
+ Intent.ACTION_PACKAGE_RESTARTED, Intent.ACTION_PACKAGE_DATA_CLEARED));
+
+ mDevice.executeShellCommand("pm clear " + pkg);
+ assertTrue(pkg + " didn't restart", count.await(10, TimeUnit.SECONDS));
+ mTargetContext.unregisterReceiver(broadcastReceiver);
+ }
+
// Annotation for tests that need to be run in portrait and landscape modes.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@@ -130,7 +157,8 @@
@Rule
public TestRule mOrderSensitiveRules = RuleChain.
- outerRule(mActivityMonitor).
+ outerRule(new TestStabilityRule()).
+ around(mActivityMonitor).
around(getRulesInsideActivityMonitor());
public UiDevice getDevice() {
@@ -144,8 +172,6 @@
mTargetContext = InstrumentationRegistry.getTargetContext();
mTargetPackage = mTargetContext.getPackageName();
- // Unlock the phone
- mDevice.executeShellCommand("input keyevent 82");
}
@After
@@ -162,46 +188,14 @@
}
}
- protected void lockRotation(boolean naturalOrientation) throws RemoteException {
- if (naturalOrientation) {
- mDevice.setOrientationNatural();
- } else {
- mDevice.setOrientationRight();
- }
- }
-
- protected void clearLauncherData() throws IOException {
+ protected void clearLauncherData() throws IOException, InterruptedException {
if (TestHelpers.isInLauncherProcess()) {
LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
resetLoaderState();
} else {
- mDevice.executeShellCommand("pm clear " + mDevice.getLauncherPackageName());
- }
- }
-
- /**
- * Scrolls the {@param container} until it finds an object matching {@param condition}.
- *
- * @return the matching object.
- */
- protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) {
- final int margin = ResourceUtils.getNavbarSize(
- ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1;
- container.setGestureMargins(0, 0, 0, margin);
-
- int i = 0;
- for (; ; ) {
- // findObject can only execute after spring settles.
- mDevice.wait(Until.findObject(condition), SHORT_UI_TIMEOUT);
- UiObject2 widget = container.findObject(condition);
- if (widget != null && widget.getVisibleBounds().intersects(
- 0, 0, mDevice.getDisplayWidth(),
- mDevice.getDisplayHeight() - margin)) {
- return widget;
- }
- if (++i > 40) fail("Too many attempts");
- container.scroll(Direction.DOWN, 1f);
+ clearPackageData(mDevice.getLauncherPackageName());
+ mLauncher.enableDebugTracing();
}
}
@@ -275,6 +269,12 @@
// Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
// flakiness.
+ protected <T> T getOnceNotNull(String message, Function<Launcher, T> f) {
+ return getOnceNotNull(message, f, DEFAULT_ACTIVITY_TIMEOUT);
+ }
+
+ // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
+ // flakiness.
protected void waitForLauncherCondition(
String message, Function<Launcher, Boolean> condition, long timeout) {
if (!TestHelpers.isInLauncherProcess()) return;
@@ -283,6 +283,20 @@
// Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
// flakiness.
+ protected <T> T getOnceNotNull(String message, Function<Launcher, T> f, long timeout) {
+ if (!TestHelpers.isInLauncherProcess()) return null;
+
+ final Object[] output = new Object[1];
+ Wait.atMost(message, () -> {
+ final Object fromLauncher = getFromLauncher(f);
+ output[0] = fromLauncher;
+ return fromLauncher != null;
+ }, timeout);
+ return (T) output[0];
+ }
+
+ // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
+ // flakiness.
protected void waitForLauncherCondition(
String message,
Runnable testThreadAction, Function<Launcher, Boolean> condition,
@@ -331,30 +345,33 @@
}
protected void startAppFast(String packageName) {
- final Instrumentation instrumentation = getInstrumentation();
- final Intent intent = instrumentation.getContext().getPackageManager().
- getLaunchIntentForPackage(packageName);
- intent.addCategory(Intent.CATEGORY_LAUNCHER);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- instrumentation.getTargetContext().startActivity(intent);
- assertTrue(packageName + " didn't start",
- mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), DEFAULT_UI_TIMEOUT));
+ startIntent(
+ getInstrumentation().getContext().getPackageManager().getLaunchIntentForPackage(
+ packageName),
+ By.pkg(packageName).depth(0),
+ true /* newTask */);
}
protected void startTestActivity(int activityNumber) {
final String packageName = getAppPackageName();
- final Instrumentation instrumentation = getInstrumentation();
- final Intent intent = instrumentation.getContext().getPackageManager().
+ final Intent intent = getInstrumentation().getContext().getPackageManager().
getLaunchIntentForPackage(packageName);
- intent.addCategory(Intent.CATEGORY_LAUNCHER);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setComponent(new ComponentName(packageName,
"com.android.launcher3.tests.Activity" + activityNumber));
- instrumentation.getTargetContext().startActivity(intent);
- assertTrue(packageName + " didn't start",
- mDevice.wait(
- Until.hasObject(By.pkg(packageName).text("TestActivity" + activityNumber)),
- DEFAULT_UI_TIMEOUT));
+ startIntent(intent, By.pkg(packageName).text("TestActivity" + activityNumber),
+ false /* newTask */);
+ }
+
+ private void startIntent(Intent intent, BySelector selector, boolean newTask) {
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ if (newTask) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ } else {
+ intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+ }
+ getInstrumentation().getTargetContext().startActivity(intent);
+ assertTrue("App didn't start: " + selector,
+ mDevice.wait(Until.hasObject(selector), DEFAULT_UI_TIMEOUT));
}
public static String resolveSystemApp(String category) {
@@ -387,4 +404,68 @@
protected int getAllAppsScroll(Launcher launcher) {
return launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY();
}
+
+ private static void checkLauncherIntegrity(
+ Launcher launcher, ContainerType expectedContainerType) {
+ if (launcher != null) {
+ final LauncherStateManager stateManager = launcher.getStateManager();
+ final LauncherState stableState = stateManager.getCurrentStableState();
+
+ assertTrue("Stable state != state: " + stableState.getClass().getSimpleName() + ", "
+ + stateManager.getState().getClass().getSimpleName(),
+ stableState == stateManager.getState());
+
+ final boolean isResumed = launcher.hasBeenResumed();
+ assertTrue("hasBeenResumed() != isStarted(), hasBeenResumed(): " + isResumed,
+ isResumed == launcher.isStarted());
+ assertTrue("hasBeenResumed() != isUserActive(), hasBeenResumed(): " + isResumed,
+ isResumed == launcher.isUserActive());
+
+ final int ordinal = stableState.ordinal;
+
+ switch (expectedContainerType) {
+ case WORKSPACE:
+ case WIDGETS: {
+ assertTrue(
+ "Launcher is not resumed in state: " + expectedContainerType,
+ isResumed);
+ assertTrue(TestProtocol.stateOrdinalToString(ordinal),
+ ordinal == TestProtocol.NORMAL_STATE_ORDINAL);
+ break;
+ }
+ case ALL_APPS: {
+ assertTrue(
+ "Launcher is not resumed in state: " + expectedContainerType,
+ isResumed);
+ assertTrue(TestProtocol.stateOrdinalToString(ordinal),
+ ordinal == TestProtocol.ALL_APPS_STATE_ORDINAL);
+ break;
+ }
+ case OVERVIEW: {
+ assertTrue(
+ "Launcher is not resumed in state: " + expectedContainerType,
+ isResumed);
+ assertTrue(TestProtocol.stateOrdinalToString(ordinal),
+ ordinal == TestProtocol.OVERVIEW_STATE_ORDINAL);
+ break;
+ }
+ case BACKGROUND: {
+ assertTrue("Launcher is resumed in state: " + expectedContainerType,
+ !isResumed);
+ assertTrue(TestProtocol.stateOrdinalToString(ordinal),
+ ordinal == TestProtocol.NORMAL_STATE_ORDINAL);
+ break;
+ }
+ default:
+ throw new IllegalArgumentException(
+ "Illegal container: " + expectedContainerType);
+ }
+ } else {
+ assertTrue(
+ "Container type is not BACKGROUND or FALLBACK_OVERVIEW: "
+ + expectedContainerType,
+ expectedContainerType == ContainerType.BACKGROUND ||
+ expectedContainerType == ContainerType.FALLBACK_OVERVIEW);
+ }
+ }
}
diff --git a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
index 58c74ce..ff21880 100644
--- a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
+++ b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
@@ -72,8 +72,7 @@
writeLayout(new LauncherLayoutBuilder().atHotseat(0).putApp(SETTINGS_APP, SETTINGS_APP));
// Launch the home activity
- mActivityMonitor.startLauncher();
- waitForModelLoaded();
+ mDevice.pressHome();
mLauncher.getWorkspace().getHotseatAppIcon(getSettingsApp().getLabel().toString());
}
@@ -88,8 +87,7 @@
info.getComponent().getClassName(), 2, 2));
// Launch the home activity
- mActivityMonitor.startLauncher();
- waitForModelLoaded();
+ mDevice.pressHome();
// Verify widget present
assertTrue("Widget is not present",
@@ -105,8 +103,7 @@
.build());
// Launch the home activity
- mActivityMonitor.startLauncher();
- waitForModelLoaded();
+ mDevice.pressHome();
mLauncher.getWorkspace().getHotseatFolder("Folder: Copy");
}
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index 0f36292..80bb3ed 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -1,5 +1,6 @@
package com.android.launcher3.ui;
+import android.util.Log;
import android.view.Surface;
import com.android.launcher3.tapl.TestHelpers;
@@ -9,6 +10,7 @@
import org.junit.runners.model.Statement;
class PortraitLandscapeRunner implements TestRule {
+ private static final String TAG = "PortraitLandscapeRunner";
private AbstractLauncherUiTest mTest;
public PortraitLandscapeRunner(AbstractLauncherUiTest test) {
@@ -36,11 +38,17 @@
evaluateInPortrait();
evaluateInLandscape();
+ } catch (Exception e) {
+ Log.e(TAG, "Exception", e);
+ throw e;
} finally {
mTest.mDevice.setOrientationNatural();
mTest.executeOnLauncher(launcher ->
- launcher.getRotationHelper().forceAllowRotationForTesting(
- false));
+ {
+ if (launcher != null) {
+ launcher.getRotationHelper().forceAllowRotationForTesting(false);
+ }
+ });
mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0);
}
}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index c3168f8..2cf6c2b 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -24,6 +24,8 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import android.util.Log;
+
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
@@ -34,9 +36,9 @@
import com.android.launcher3.tapl.AppIcon;
import com.android.launcher3.tapl.AppIconMenu;
import com.android.launcher3.tapl.AppIconMenuItem;
-import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.tapl.Widgets;
import com.android.launcher3.tapl.Workspace;
+import com.android.launcher3.util.rule.TestStabilityRule.Stability;
import com.android.launcher3.views.OptionsPopupView;
import com.android.launcher3.widget.WidgetsFullSheet;
import com.android.launcher3.widget.WidgetsRecyclerView;
@@ -190,7 +192,7 @@
launcher -> assertTrue("ensureScrollable didn't make workspace scrollable",
isWorkspaceScrollable(launcher)));
assertNotNull("ensureScrollable didn't add Chrome app",
- workspace.tryGetWorkspaceAppIcon("Chrome"));
+ workspace.getWorkspaceAppIcon("Chrome"));
// Test flinging workspace.
workspace.flingBackward();
@@ -206,7 +208,7 @@
assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL));
// Test starting a workspace app.
- final AppIcon app = workspace.tryGetWorkspaceAppIcon("Chrome");
+ final AppIcon app = workspace.getWorkspaceAppIcon("Chrome");
assertNotNull("No Chrome app in workspace", app);
}
@@ -216,7 +218,8 @@
final AppIcon app = allApps.getAppIcon("TestActivity7");
assertNotNull("AppIcon.launch returned null", app.launch(getAppPackageName()));
test.executeOnLauncher(launcher -> assertTrue(
- "Launcher activity is the top activity; expecting another activity to be the top "
+ "Launcher activity is the top activity; expecting another activity to be the "
+ + "top "
+ "one",
test.isInBackground(launcher)));
} finally {
@@ -304,11 +307,8 @@
switchToAllApps();
allApps.freeze();
try {
- allApps.
- getAppIcon(APP_NAME).
- dragToWorkspace().
- getWorkspaceAppIcon(APP_NAME).
- launch(getAppPackageName());
+ allApps.getAppIcon(APP_NAME).dragToWorkspace();
+ mLauncher.getWorkspace().getWorkspaceAppIcon(APP_NAME).launch(getAppPackageName());
} finally {
allApps.unfreeze();
}
@@ -335,13 +335,8 @@
getMenuItem(0);
final String shortcutName = menuItem.getText();
- // 4. Drag the first shortcut to the home screen.
- // 5. Verify that the shortcut works on home screen
- // (the app opens and has the same text as the shortcut).
- menuItem.
- dragToWorkspace().
- getWorkspaceAppIcon(shortcutName).
- launch(getAppPackageName());
+ menuItem.dragToWorkspace();
+ mLauncher.getWorkspace().getWorkspaceAppIcon(shortcutName).launch(getAppPackageName());
} finally {
allApps.unfreeze();
}
@@ -350,4 +345,10 @@
public static String getAppPackageName() {
return getInstrumentation().getContext().getPackageName();
}
+
+ @Test
+ @Stability
+ public void testTestStabilityAttribute() {
+ Log.d("TestStabilityRule", "Hello world!");
+ }
}
diff --git a/tests/src/com/android/launcher3/ui/TestViewHelpers.java b/tests/src/com/android/launcher3/ui/TestViewHelpers.java
index d13d319..d0df664 100644
--- a/tests/src/com/android/launcher3/ui/TestViewHelpers.java
+++ b/tests/src/com/android/launcher3/ui/TestViewHelpers.java
@@ -18,28 +18,15 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static androidx.test.InstrumentationRegistry.getTargetContext;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
import android.content.ComponentName;
-import android.content.Context;
-import android.graphics.Point;
import android.os.Process;
-import android.os.SystemClock;
import android.util.Log;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.R;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.testcomponent.AppWidgetNoConfig;
import com.android.launcher3.testcomponent.AppWidgetWithConfig;
@@ -50,21 +37,6 @@
public class TestViewHelpers {
private static final String TAG = "TestViewHelpers";
- private static UiDevice getDevice() {
- return UiDevice.getInstance(getInstrumentation());
- }
-
- public static UiObject2 findViewById(int id) {
- return getDevice().wait(Until.findObject(getSelectorForId(id)),
- AbstractLauncherUiTest.DEFAULT_UI_TIMEOUT);
- }
-
- public static BySelector getSelectorForId(int id) {
- final Context targetContext = getTargetContext();
- String name = targetContext.getResources().getResourceEntryName(id);
- return By.res(targetContext.getPackageName(), name);
- }
-
/**
* Finds a widget provider which can fit on the home screen.
*
@@ -91,92 +63,6 @@
return info;
}
- /**
- * Drags an icon to the center of homescreen.
- *
- * @param icon object that is either app icon or shortcut icon
- */
- public static void dragToWorkspace(UiObject2 icon, boolean expectedToShowShortcuts) {
- Point center = icon.getVisibleCenter();
-
- // Action Down
- final long downTime = SystemClock.uptimeMillis();
- sendPointer(downTime, MotionEvent.ACTION_DOWN, center);
-
- UiObject2 dragLayer = findViewById(R.id.drag_layer);
-
- if (expectedToShowShortcuts) {
- // Make sure shortcuts show up, and then move a bit to hide them.
- assertNotNull(findViewById(R.id.deep_shortcuts_container));
-
- Point moveLocation = new Point(center);
- int distanceToMove =
- getTargetContext().getResources().getDimensionPixelSize(
- R.dimen.deep_shortcuts_start_drag_threshold) + 50;
- if (moveLocation.y - distanceToMove >= dragLayer.getVisibleBounds().top) {
- moveLocation.y -= distanceToMove;
- } else {
- moveLocation.y += distanceToMove;
- }
- movePointer(downTime, center, moveLocation);
-
- assertNull(findViewById(R.id.deep_shortcuts_container));
- }
-
- // Wait until Remove/Delete target is visible
- assertNotNull(findViewById(R.id.delete_target_text));
-
- Point moveLocation = dragLayer.getVisibleCenter();
-
- // Move to center
- movePointer(downTime, center, moveLocation);
- sendPointer(downTime, MotionEvent.ACTION_UP, moveLocation);
-
- // Wait until remove target is gone.
- getDevice().wait(Until.gone(getSelectorForId(R.id.delete_target_text)),
- AbstractLauncherUiTest.DEFAULT_UI_TIMEOUT);
- }
-
- private static void movePointer(long downTime, Point from, Point to) {
- while (!from.equals(to)) {
- try {
- Thread.sleep(20);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- from.x = getNextMoveValue(to.x, from.x);
- from.y = getNextMoveValue(to.y, from.y);
- sendPointer(downTime, MotionEvent.ACTION_MOVE, from);
- }
- }
-
- private static int getNextMoveValue(int targetValue, int oldValue) {
- if (targetValue - oldValue > 10) {
- return oldValue + 10;
- } else if (targetValue - oldValue < -10) {
- return oldValue - 10;
- } else {
- return targetValue;
- }
- }
-
- public static void sendPointer(long downTime, int action, Point point) {
- MotionEvent event = MotionEvent.obtain(downTime,
- SystemClock.uptimeMillis(), action, point.x, point.y, 0);
- getInstrumentation().sendPointerSync(event);
- event.recycle();
- }
-
- /**
- * Opens widget tray and returns the recycler view.
- */
- public static UiObject2 openWidgetsTray() {
- final UiDevice device = getDevice();
- device.pressKeyCode(KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON);
- device.waitForIdle();
- return findViewById(R.id.widgets_list_view);
- }
-
public static View findChildView(ViewGroup parent, Function<View, Boolean> condition) {
for (int i = 0; i < parent.getChildCount(); ++i) {
final View child = parent.getChildAt(i);
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index c93c20a..d9edc35 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -53,8 +53,8 @@
@Test
public void workTabExists() {
- mActivityMonitor.startLauncher();
-
+ mDevice.pressHome();
+ waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
/*
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index 5eb5f19..e1b3ede 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -27,23 +27,20 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Workspace;
+import com.android.launcher3.tapl.Widgets;
import com.android.launcher3.testcomponent.WidgetConfigActivity;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.Condition;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.widget.WidgetCell;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -55,7 +52,8 @@
@RunWith(AndroidJUnit4.class)
public class AddConfigWidgetTest extends AbstractLauncherUiTest {
- @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
+ @Rule
+ public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
private LauncherAppWidgetProviderInfo mWidgetInfo;
private AppWidgetManager mAppWidgetManager;
@@ -71,58 +69,35 @@
}
@Test
- // Convert test to TAPL b/131116002
+ @PortraitLandscape
public void testWidgetConfig() throws Throwable {
- runTest(false, true);
+ runTest(true);
}
@Test
- @Ignore // b/121280703
- public void testWidgetConfig_rotate() throws Throwable {
- runTest(true, true);
- }
-
- @Test
- // Convert test to TAPL b/131116002
+ @PortraitLandscape
public void testConfigCancelled() throws Throwable {
- runTest(false, false);
+ runTest(false);
}
- @Test
- @Ignore // b/121280703
- public void testConfigCancelled_rotate() throws Throwable {
- runTest(true, false);
- }
/**
- * @param rotateConfig should the config screen be rotated
* @param acceptConfig accept the config activity
*/
- private void runTest(boolean rotateConfig, boolean acceptConfig) throws Throwable {
- lockRotation(true);
-
+ private void runTest(boolean acceptConfig) throws Throwable {
clearHomescreen();
- mActivityMonitor.startLauncher();
+ mDevice.pressHome();
- // Open widget tray and wait for load complete.
- final UiObject2 widgetContainer = TestViewHelpers.openWidgetsTray();
- Wait.atMost(null, Condition.minChildCount(widgetContainer, 2), DEFAULT_UI_TIMEOUT);
+ final Widgets widgets = mLauncher.getWorkspace().openAllWidgets();
// Drag widget to homescreen
WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
- UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class)
- .hasDescendant(By.text(mWidgetInfo.getLabel(mTargetContext.getPackageManager()))));
- TestViewHelpers.dragToWorkspace(widget, false);
+ widgets.
+ getWidget(mWidgetInfo.getLabel(mTargetContext.getPackageManager())).
+ dragToWorkspace();
// Widget id for which the config activity was opened
mWidgetId = monitor.getWidgetId();
- if (rotateConfig) {
- // Rotate the screen and verify that the config activity is recreated
- monitor = new WidgetConfigStartupMonitor();
- lockRotation(false);
- assertEquals(mWidgetId, monitor.getWidgetId());
- }
-
// Verify that the widget id is valid and bound
assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 0061568..f9d1d93 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -15,26 +15,21 @@
*/
package com.android.launcher3.ui.widget;
+import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
+
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
-import android.view.View;
-import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.tapl.Widget;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
-import com.android.launcher3.util.Condition;
-import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.widget.WidgetCell;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,45 +41,50 @@
@RunWith(AndroidJUnit4.class)
public class AddWidgetTest extends AbstractLauncherUiTest {
- @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
+ @Rule
+ public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
@Test
- public void testDragIcon_portrait() throws Throwable {
- lockRotation(true);
- performTest();
- }
-
- @Test
- @Ignore // b/121280703
- public void testDragIcon_landscape() throws Throwable {
- lockRotation(false);
- performTest();
- }
-
- // Convert to TAPL b/131116002
- private void performTest() throws Throwable {
+ @PortraitLandscape
+ public void testDragIcon() throws Throwable {
clearHomescreen();
- mActivityMonitor.startLauncher();
+ mDevice.pressHome();
final LauncherAppWidgetProviderInfo widgetInfo =
TestViewHelpers.findWidgetProvider(this, false /* hasConfigureScreen */);
- // Open widget tray and wait for load complete.
- final UiObject2 widgetContainer = TestViewHelpers.openWidgetsTray();
- Wait.atMost(null, Condition.minChildCount(widgetContainer, 2), DEFAULT_UI_TIMEOUT);
+ mLauncher.
+ getWorkspace().
+ openAllWidgets().
+ getWidget(widgetInfo.getLabel(mTargetContext.getPackageManager())).
+ dragToWorkspace();
- // Drag widget to homescreen
- UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class)
- .hasDescendant(By.text(widgetInfo.getLabel(mTargetContext.getPackageManager()))));
- TestViewHelpers.dragToWorkspace(widget, false);
-
- assertTrue(mActivityMonitor.itemExists(new ItemOperator() {
- @Override
- public boolean evaluate(ItemInfo info, View view) {
- return info instanceof LauncherAppWidgetInfo &&
+ assertTrue(mActivityMonitor.itemExists(
+ (info, view) -> info instanceof LauncherAppWidgetInfo &&
((LauncherAppWidgetInfo) info).providerName.getClassName().equals(
- widgetInfo.provider.getClassName());
- }
- }).call());
+ widgetInfo.provider.getClassName())).call());
+
+ final Widget widget = mLauncher.getWorkspace().tryGetWidget(widgetInfo.label,
+ DEFAULT_UI_TIMEOUT);
+ assertNotNull("Widget not found on the workspace", widget);
+ widget.launch(getAppPackageName());
+ }
+
+ /**
+ * Test dragging a custom shortcut to the workspace and launch it.
+ *
+ * A custom shortcut is a 1x1 widget that launches a specific intent when user tap on it.
+ * Custom shortcuts are replaced by deep shortcuts after api 25.
+ */
+ @Test
+ @PortraitLandscape
+ public void testDragCustomShortcut() throws Throwable {
+ clearHomescreen();
+ mDevice.pressHome();
+ mLauncher.getWorkspace().openAllWidgets()
+ .getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity")
+ .dragToWorkspace();
+ mLauncher.getWorkspace().getWorkspaceAppIcon("Shortcut")
+ .launch(getAppPackageName());
}
}
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index d36126b..f42bf1f 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -44,6 +44,7 @@
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.rule.ShellCommandRule;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.WidgetHostViewLoader;
@@ -54,7 +55,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.HashSet;
import java.util.Set;
+import java.util.function.Consumer;
/**
* Tests for bind widget flow.
@@ -267,8 +270,7 @@
resetLoaderState();
// Launch the home activity
- mActivityMonitor.startLauncher();
- waitForModelLoaded();
+ mDevice.pressHome();
}
private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
@@ -326,9 +328,12 @@
int count = 0;
String pkg = invalidPackage;
- Set<String> activePackage = getOnUiThread(() ->
- PackageInstallerCompat.getInstance(mTargetContext)
- .updateAndGetActiveSessionCache().keySet());
+ Set<String> activePackage = getOnUiThread(() -> {
+ Set<String> packages = new HashSet<>();
+ PackageInstallerCompat.getInstance(mTargetContext).updateAndGetActiveSessionCache()
+ .keySet().forEach(packageUserKey -> packages.add(packageUserKey.mPackageName));
+ return packages;
+ });
while(true) {
try {
mTargetContext.getPackageManager().getPackageInfo(
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 6122dae..07129dd 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -128,10 +128,8 @@
if (!Utilities.ATLEAST_OREO) {
return;
}
- lockRotation(true);
-
clearHomescreen();
- mActivityMonitor.startLauncher();
+ mDevice.pressHome();
// Open Pin item activity
BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver(
diff --git a/tests/src/com/android/launcher3/util/Condition.java b/tests/src/com/android/launcher3/util/Condition.java
index b564a1a..d85dd3a 100644
--- a/tests/src/com/android/launcher3/util/Condition.java
+++ b/tests/src/com/android/launcher3/util/Condition.java
@@ -1,8 +1,8 @@
package com.android.launcher3.util;
-import androidx.test.uiautomator.UiObject2;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import com.android.launcher3.MainThreadExecutor;
+import androidx.test.uiautomator.UiObject2;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -16,7 +16,7 @@
* Converts the condition to be run on UI thread.
*/
static Condition runOnUiThread(final Condition condition) {
- final MainThreadExecutor executor = new MainThreadExecutor();
+ final LooperExecutor executor = MAIN_EXECUTOR;
return () -> {
final AtomicBoolean value = new AtomicBoolean(false);
final Throwable[] exceptions = new Throwable[1];
diff --git a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
index 0235f95..8f89173 100644
--- a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
+++ b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
@@ -16,6 +16,7 @@
package com.android.launcher3.util;
+import static com.android.launcher3.util.Executors.createAndStartNewLooper;
import static com.android.launcher3.util.RaceConditionTracker.ENTER_POSTFIX;
import static com.android.launcher3.util.RaceConditionTracker.EXIT_POSTFIX;
@@ -23,7 +24,6 @@
import static org.junit.Assert.fail;
import android.os.Handler;
-import android.os.HandlerThread;
import android.util.Log;
import java.util.ArrayList;
@@ -72,9 +72,7 @@
private static final Handler POSTPONED_EVENT_RESUME_HANDLER = createEventResumeHandler();
private static Handler createEventResumeHandler() {
- final HandlerThread thread = new HandlerThread("RaceConditionEventResumer");
- thread.start();
- return new Handler(thread.getLooper());
+ return new Handler(createAndStartNewLooper("RaceConditionEventResumer"));
}
/**
diff --git a/tests/src/com/android/launcher3/util/Wait.java b/tests/src/com/android/launcher3/util/Wait.java
index 593cce8..899686b 100644
--- a/tests/src/com/android/launcher3/util/Wait.java
+++ b/tests/src/com/android/launcher3/util/Wait.java
@@ -1,6 +1,7 @@
package com.android.launcher3.util;
import android.os.SystemClock;
+import android.util.Log;
import org.junit.Assert;
@@ -16,7 +17,9 @@
}
public static void atMost(String message, Condition condition, long timeout, long sleepMillis) {
- long endTime = SystemClock.uptimeMillis() + timeout;
+ final long startTime = SystemClock.uptimeMillis();
+ long endTime = startTime + timeout;
+ Log.d("Wait", "atMost: " + startTime + " - " + endTime);
while (SystemClock.uptimeMillis() < endTime) {
try {
if (condition.isTrue()) {
@@ -36,6 +39,7 @@
} catch (Throwable t) {
throw new RuntimeException(t);
}
+ Log.d("Wait", "atMost: timed out: " + SystemClock.uptimeMillis());
Assert.fail(message);
}
}
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index eef2f24..cdda0f0 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -15,17 +15,16 @@
public class FailureWatcher extends TestWatcher {
private static final String TAG = "FailureWatcher";
- private static int sScreenshotCount = 0;
final private UiDevice mDevice;
public FailureWatcher(UiDevice device) {
mDevice = device;
}
- private void dumpViewHierarchy() {
+ private static void dumpViewHierarchy(UiDevice device) {
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
try {
- mDevice.dumpWindowHierarchy(stream);
+ device.dumpWindowHierarchy(stream);
stream.flush();
stream.close();
for (String line : stream.toString().split("\\r?\\n")) {
@@ -38,22 +37,27 @@
@Override
protected void failed(Throwable e, Description description) {
- if (mDevice == null) return;
+ onError(mDevice, description, e);
+ }
+
+ public static void onError(UiDevice device, Description description, Throwable e) {
+ if (device == null) return;
final String pathname = getInstrumentation().getTargetContext().
- getFilesDir().getPath() + "/TaplTestScreenshot" + sScreenshotCount++ + ".png";
+ getFilesDir().getPath() + "/TestScreenshot-" + description.getMethodName()
+ + ".png";
Log.e(TAG, "Failed test " + description.getMethodName() +
", screenshot will be saved to " + pathname +
", track trace is below, UI object dump is further below:\n" +
Log.getStackTraceString(e));
- dumpViewHierarchy();
+ dumpViewHierarchy(device);
try {
- final String dumpsysResult = mDevice.executeShellCommand(
+ final String dumpsysResult = device.executeShellCommand(
"dumpsys activity service TouchInteractionService");
Log.d(TAG, "TouchInteractionService: " + dumpsysResult);
} catch (IOException ex) {
}
- mDevice.takeScreenshot(new File(pathname));
+ device.takeScreenshot(new File(pathname));
}
}
diff --git a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
index 2aba7a5..62fe26d 100644
--- a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
@@ -15,15 +15,11 @@
*/
package com.android.launcher3.util.rule;
-import static com.android.launcher3.tapl.TestHelpers.getHomeIntentInPackage;
-
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-import static androidx.test.InstrumentationRegistry.getTargetContext;
-
import android.app.Activity;
import android.app.Application;
import android.app.Application.ActivityLifecycleCallbacks;
import android.os.Bundle;
+
import androidx.test.InstrumentationRegistry;
import com.android.launcher3.Launcher;
@@ -52,26 +48,15 @@
}
public Callable<Boolean> itemExists(final ItemOperator op) {
- return new Callable<Boolean>() {
-
- @Override
- public Boolean call() throws Exception {
- Launcher launcher = getActivity();
- if (launcher == null) {
- return false;
- }
- return launcher.getWorkspace().getFirstMatch(op) != null;
+ return () -> {
+ Launcher launcher = getActivity();
+ if (launcher == null) {
+ return false;
}
+ return launcher.getWorkspace().getFirstMatch(op) != null;
};
}
- /**
- * Starts the launcher activity in the target package.
- */
- public void startLauncher() {
- getInstrumentation().startActivitySync(getHomeIntentInPackage(getTargetContext()));
- }
-
private class MyStatement extends Statement implements ActivityLifecycleCallbacks {
private final Statement mBase;
@@ -100,19 +85,27 @@
}
@Override
- public void onActivityStarted(Activity activity) { }
+ public void onActivityStarted(Activity activity) {
+ if (activity instanceof Launcher) {
+ mActivity.getRotationHelper().forceAllowRotationForTesting(true);
+ }
+ }
@Override
- public void onActivityResumed(Activity activity) { }
+ public void onActivityResumed(Activity activity) {
+ }
@Override
- public void onActivityPaused(Activity activity) { }
+ public void onActivityPaused(Activity activity) {
+ }
@Override
- public void onActivityStopped(Activity activity) { }
+ public void onActivityStopped(Activity activity) {
+ }
@Override
- public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
+ public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
+ }
@Override
public void onActivityDestroyed(Activity activity) {
diff --git a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
new file mode 100644
index 0000000..d7f41bf
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2019 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.rule;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import android.os.Build;
+import android.util.Log;
+
+import androidx.test.uiautomator.UiDevice;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class TestStabilityRule implements TestRule {
+ private static final String TAG = "TestStabilityRule";
+ private static final Pattern LAUNCHER_BUILD =
+ Pattern.compile("^("
+ + "(?<local>(BuildFromAndroidStudio|"
+ + "([0-9]+|[A-Z])-eng\\.[a-z]+\\.[0-9]+\\.[0-9]+))|"
+ + "(?<presubmit>([0-9]+|[A-Z])-P[0-9]+)|"
+ + "(?<postsubmit>([0-9]+|[A-Z])-[0-9]+)|"
+ + "(?<platform>[0-9]+|[A-Z])"
+ + ")$");
+ private static final Pattern PLATFORM_BUILD =
+ Pattern.compile("^("
+ + "(?<commandLine>eng\\.[a-z]+\\.[0-9]+\\.[0-9]+)|"
+ + "(?<presubmit>P[0-9]+)|"
+ + "(?<postsubmit>[0-9]+)"
+ + ")$");
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface Stability {
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ if (description.getAnnotation(Stability.class) != null) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ getRunFlavor();
+
+ base.evaluate();
+ }
+ };
+ } else {
+ return base;
+ }
+ }
+
+ private static void getRunFlavor() throws Exception {
+ final String launcherVersion = getInstrumentation().
+ getContext().
+ getPackageManager().
+ getPackageInfo(
+ UiDevice.getInstance(getInstrumentation()).
+ getLauncherPackageName(),
+ 0).
+ versionName;
+
+ final Matcher launcherBuildMatcher = LAUNCHER_BUILD.matcher(launcherVersion);
+
+ if (!launcherBuildMatcher.find()) {
+ Log.e(TAG, "Match not found");
+ }
+
+ final String platformVersion = Build.VERSION.INCREMENTAL;
+ final Matcher platformBuildMatcher = PLATFORM_BUILD.matcher(platformVersion);
+
+ if (!platformBuildMatcher.find()) {
+ Log.e(TAG, "Match not found");
+ }
+
+ Log.d(TAG, "Launcher: " + launcherVersion + ", platform: " + platformVersion);
+
+ if (launcherBuildMatcher.group("local") != null && (
+ platformBuildMatcher.group("commandLine") != null ||
+ platformBuildMatcher.group("postsubmit") != null)) {
+ Log.d(TAG, "LOCAL RUN");
+ } else if (launcherBuildMatcher.group("presubmit") != null
+ && platformBuildMatcher.group("postsubmit") != null) {
+ Log.d(TAG, "UNBUNDLED PRESUBMIT");
+ } else if (launcherBuildMatcher.group("postsubmit") != null
+ && platformBuildMatcher.group("postsubmit") != null) {
+ Log.d(TAG, "UNBUNDLED POSTSUBMIT");
+ } else if (launcherBuildMatcher.group("platform") != null
+ && platformBuildMatcher.group("presubmit") != null) {
+ Log.d(TAG, "PLATFORM PRESUBMIT");
+ } else if (launcherBuildMatcher.group("platform") != null
+ && platformBuildMatcher.group("postsubmit") != null) {
+ Log.d(TAG, "PLATFORM POSTSUBMIT");
+ } else {
+ Log.e(TAG, "ERROR3");
+ }
+ }
+}
diff --git a/tests/tapl/AndroidManifest.xml b/tests/tapl/AndroidManifest.xml
index 0207e2b..1065446 100644
--- a/tests/tapl/AndroidManifest.xml
+++ b/tests/tapl/AndroidManifest.xml
@@ -23,4 +23,6 @@
>
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <uses-permission android:name="android.permission.READ_LOGS"/>
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
</manifest>
diff --git a/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
index 7f561a2..03d1600 100644
--- a/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
+++ b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
@@ -16,10 +16,16 @@
package com.android.launcher3.tapl;
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiObject2;
+import java.util.regex.Pattern;
+
public class AddToHomeScreenPrompt {
+ private static final Pattern ADD_AUTOMATICALLY =
+ Pattern.compile("^Add automatically$", CASE_INSENSITIVE);
private final LauncherInstrumentation mLauncher;
private final UiObject2 mWidgetCell;
@@ -33,9 +39,6 @@
public void addAutomatically() {
mLauncher.waitForObjectInContainer(
mWidgetCell.getParent().getParent().getParent().getParent(),
- By.text(LauncherInstrumentation.isAvd()
- ? "ADD AUTOMATICALLY"
- : "Add automatically")).
- click();
+ By.text(ADD_AUTOMATICALLY)).click();
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 9ff354a..96e4b8c 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -36,6 +36,7 @@
private static final int MAX_SCROLL_ATTEMPTS = 40;
private final int mHeight;
+ private final int mIconHeight;
AllApps(LauncherInstrumentation launcher) {
super(launcher);
@@ -46,6 +47,10 @@
// Wait for the recycler to populate.
mLauncher.waitForObjectInContainer(appListRecycler, By.clazz(TextView.class));
verifyNotFrozen("All apps freeze flags upon opening all apps");
+ mIconHeight = mLauncher.getTestInfo(
+ TestProtocol.REQUEST_ICON_HEIGHT)
+ .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+
}
@Override
@@ -62,6 +67,10 @@
}
final Rect iconBounds = icon.getVisibleBounds();
LauncherInstrumentation.log("hasClickableIcon: icon bounds: " + iconBounds);
+ if (iconBounds.height() < mIconHeight / 2) {
+ LauncherInstrumentation.log("hasClickableIcon: icon has insufficient height");
+ return false;
+ }
if (iconCenterInSearchBox(allAppsContainer, icon)) {
LauncherInstrumentation.log("hasClickableIcon: icon center is under search box");
return false;
@@ -90,6 +99,7 @@
final UiObject2 allAppsContainer = verifyActiveContainer();
final UiObject2 appListRecycler = mLauncher.waitForObjectInContainer(allAppsContainer,
"apps_list_view");
+ final UiObject2 searchBox = getSearchBox(allAppsContainer);
allAppsContainer.setGestureMargins(
0,
getSearchBox(allAppsContainer).getVisibleBounds().bottom + 1,
@@ -103,7 +113,11 @@
int scroll = getScroll(allAppsContainer);
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled")) {
while (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector)) {
- mLauncher.scroll(allAppsContainer, Direction.DOWN, 0.8f, null, 50);
+ mLauncher.scrollToLastVisibleRow(
+ allAppsContainer,
+ mLauncher.getObjectsInContainer(allAppsContainer, "icon"),
+ searchBox.getVisibleBounds().bottom -
+ allAppsContainer.getVisibleBounds().top);
final int newScroll = getScroll(allAppsContainer);
if (newScroll == scroll) break;
@@ -120,7 +134,7 @@
mLauncher.assertTrue("Unable to scroll to a clickable icon: " + appName,
hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector));
- final UiObject2 appIcon = mLauncher.getObjectInContainer(appListRecycler,
+ final UiObject2 appIcon = mLauncher.waitForObjectInContainer(appListRecycler,
appIconSelector);
return new AppIcon(mLauncher, appIcon);
}
@@ -145,7 +159,7 @@
"Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
++attempts <= MAX_SCROLL_ATTEMPTS);
- mLauncher.scroll(allAppsContainer, Direction.UP, 1, margins, 50);
+ mLauncher.scroll(allAppsContainer, Direction.UP, margins, 50);
}
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled up")) {
@@ -172,7 +186,7 @@
final UiObject2 allAppsContainer = verifyActiveContainer();
// Start the gesture in the center to avoid starting at elements near the top.
mLauncher.scroll(
- allAppsContainer, Direction.DOWN, 1, new Rect(0, 0, 0, mHeight / 2), 10);
+ allAppsContainer, Direction.DOWN, new Rect(0, 0, 0, mHeight / 2), 10);
verifyActiveContainer();
}
}
@@ -186,7 +200,7 @@
final UiObject2 allAppsContainer = verifyActiveContainer();
// Start the gesture in the center, for symmetry with forward.
mLauncher.scroll(
- allAppsContainer, Direction.UP, 1, new Rect(0, mHeight / 2, 0, 0), 10);
+ allAppsContainer, Direction.UP, new Rect(0, mHeight / 2, 0, 0), 10);
verifyActiveContainer();
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index c9eaf27..db3d846 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -23,6 +23,7 @@
import android.view.MotionEvent;
import androidx.annotation.NonNull;
+import androidx.test.uiautomator.UiObject2;
import com.android.launcher3.testing.TestProtocol;
@@ -54,12 +55,12 @@
"want to switch from background to overview")) {
verifyActiveContainer();
goToOverviewUnchecked(BACKGROUND_APP_STATE_ORDINAL);
- return new BaseOverview(mLauncher);
+ return mLauncher.isFallbackOverview() ?
+ new BaseOverview(mLauncher) : new Overview(mLauncher);
}
}
protected void goToOverviewUnchecked(int expectedState) {
- mLauncher.getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
switch (mLauncher.getNavigationModel()) {
case ZERO_BUTTON: {
final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
@@ -112,7 +113,60 @@
mLauncher.waitForSystemUiObject("recent_apps").click();
break;
}
- mLauncher.getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
+ }
+
+ /**
+ * Swipes right or double presses the square button to switch to the previous app.
+ */
+ public Background quickSwitchToPreviousApp() {
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to quick switch to the previous app")) {
+ verifyActiveContainer();
+ quickSwitchToPreviousApp(getExpectedStateForQuickSwitch());
+ return new Background(mLauncher);
+ }
+ }
+
+ protected int getExpectedStateForQuickSwitch() {
+ return BACKGROUND_APP_STATE_ORDINAL;
+ }
+
+ protected void quickSwitchToPreviousApp(int expectedState) {
+ boolean transposeInLandscape = false;
+ switch (mLauncher.getNavigationModel()) {
+ case TWO_BUTTON:
+ transposeInLandscape = true;
+ // Fall through, zero button and two button modes behave the same.
+ case ZERO_BUTTON: {
+ final int startX;
+ final int startY;
+ final int endX;
+ final int endY;
+ if (mLauncher.getDevice().isNaturalOrientation() || !transposeInLandscape) {
+ // Swipe from the bottom left to the bottom right of the screen.
+ startX = 0;
+ startY = getSwipeStartY();
+ endX = mLauncher.getDevice().getDisplayWidth();
+ endY = startY;
+ } else {
+ // Swipe from the bottom right to the top right of the screen.
+ startX = getSwipeStartX();
+ startY = mLauncher.getRealDisplaySize().y - 1;
+ endX = startX;
+ endY = 0;
+ }
+ mLauncher.swipeToState(startX, startY, endX, endY, 20, expectedState);
+ break;
+ }
+
+ case THREE_BUTTON:
+ // Double press the recents button.
+ UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps");
+ recentsButton.click();
+ mLauncher.getOverview();
+ recentsButton.click();
+ break;
+ }
}
protected String getSwipeHeightRequestName() {
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index ace49e9..49c0c89 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -16,11 +16,15 @@
package com.android.launcher3.tapl;
+import android.graphics.Rect;
+
import androidx.annotation.NonNull;
import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiObject2;
+import com.android.launcher3.testing.TestProtocol;
+
import java.util.Collections;
import java.util.List;
@@ -28,7 +32,6 @@
* Common overview pane for both Launcher and fallback recents
*/
public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
- private static final int FLING_SPEED = LauncherInstrumentation.isAvd() ? 500 : 1500;
private static final int FLINGS_FOR_DISMISS_LIMIT = 40;
BaseOverview(LauncherInstrumentation launcher) {
@@ -38,7 +41,7 @@
@Override
protected LauncherInstrumentation.ContainerType getContainerType() {
- return LauncherInstrumentation.ContainerType.BASE_OVERVIEW;
+ return LauncherInstrumentation.ContainerType.FALLBACK_OVERVIEW;
}
/**
@@ -49,9 +52,10 @@
mLauncher.addContextLayer("want to fling forward in overview")) {
LauncherInstrumentation.log("Overview.flingForward before fling");
final UiObject2 overview = verifyActiveContainer();
- overview.setGestureMargins(mLauncher.getEdgeSensitivityWidth(), 0, 0, 0);
- overview.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
- mLauncher.waitForIdle();
+ final int leftMargin = mLauncher.getTestInfo(
+ TestProtocol.REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN).
+ getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ mLauncher.scroll(overview, Direction.LEFT, new Rect(leftMargin, 0, 0, 0), 20);
verifyActiveContainer();
}
}
@@ -70,7 +74,7 @@
flingForward();
}
- mLauncher.getObjectInContainer(verifyActiveContainer(), clearAllSelector).click();
+ mLauncher.waitForObjectInContainer(verifyActiveContainer(), clearAllSelector).click();
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
"dismissed all tasks")) {
return new Workspace(mLauncher);
@@ -86,9 +90,10 @@
mLauncher.addContextLayer("want to fling backward in overview")) {
LauncherInstrumentation.log("Overview.flingBackward before fling");
final UiObject2 overview = verifyActiveContainer();
- overview.setGestureMargins(0, 0, mLauncher.getEdgeSensitivityWidth(), 0);
- overview.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
- mLauncher.waitForIdle();
+ final int rightMargin = mLauncher.getTestInfo(
+ TestProtocol.REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN).
+ getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ mLauncher.scroll(overview, Direction.RIGHT, new Rect(0, 0, rightMargin, 0), 20);
verifyActiveContainer();
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java
index cfc4374..e0fe933 100644
--- a/tests/tapl/com/android/launcher3/tapl/Home.java
+++ b/tests/tapl/com/android/launcher3/tapl/Home.java
@@ -17,6 +17,7 @@
package com.android.launcher3.tapl;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
import androidx.annotation.NonNull;
@@ -58,4 +59,9 @@
}
}
}
+
+ @Override
+ protected int getExpectedStateForQuickSwitch() {
+ return QUICK_SWITCH_STATE_ORDINAL;
+ }
}
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index d4bdafa..df80a51 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -16,6 +16,8 @@
package com.android.launcher3.tapl;
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+
import android.graphics.Point;
import androidx.test.uiautomator.By;
@@ -23,13 +25,10 @@
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
-import com.android.launcher3.testing.TestProtocol;
-
/**
* Ancestor for AppIcon and AppMenuItem.
*/
abstract class Launchable {
- private static final int WAIT_TIME_MS = 60000;
protected final LauncherInstrumentation mLauncher;
protected final UiObject2 mObject;
@@ -53,11 +52,12 @@
private Background launch(BySelector selector) {
LauncherInstrumentation.log("Launchable.launch before click " +
mObject.getVisibleCenter() + " in " + mObject.getVisibleBounds());
- mLauncher.getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
- mLauncher.assertTrue(
- "Launching an app didn't open a new window: " + mObject.getText(),
- mObject.clickAndWait(Until.newWindow(), WAIT_TIME_MS));
- mLauncher.getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
+
+ mLauncher.executeAndWaitForEvent(
+ () -> mObject.click(),
+ event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
+ "Launching an app didn't open a new window: " + mObject.getText());
+
mLauncher.assertTrue(
"App didn't start: " + selector,
mLauncher.getDevice().wait(Until.hasObject(selector),
@@ -68,7 +68,7 @@
/**
* Drags an object to the center of homescreen.
*/
- public Workspace dragToWorkspace() {
+ public void dragToWorkspace() {
final Point launchableCenter = getObject().getVisibleCenter();
final Point displaySize = mLauncher.getRealDisplaySize();
final int width = displaySize.x / 2;
@@ -80,10 +80,6 @@
launchableCenter.x - width / 2 : launchableCenter.x + width / 2,
displaySize.y / 2),
getLongPressIndicator());
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "dragged launchable to workspace")) {
- return new Workspace(mLauncher);
- }
}
protected abstract String getLongPressIndicator();
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index a7e6336..c52650d 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -37,7 +37,6 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.SystemClock;
@@ -52,6 +51,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.Configurator;
@@ -60,6 +60,7 @@
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
+import com.android.launcher3.ResourceUtils;
import com.android.launcher3.testing.TestProtocol;
import com.android.systemui.shared.system.QuickStepContract;
@@ -68,10 +69,14 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
+import java.util.Collection;
+import java.util.Collections;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
+import java.util.function.Function;
/**
* The main tapl object. The only object that can be explicitly constructed by the using code. It
@@ -82,11 +87,12 @@
private static final String TAG = "Tapl";
private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 20;
private static final int GESTURE_STEP_MS = 16;
+ private static long START_TIME = System.currentTimeMillis();
// Types for launcher containers that the user is interacting with. "Background" is a
// pseudo-container corresponding to inactive launcher covered by another app.
- enum ContainerType {
- WORKSPACE, ALL_APPS, OVERVIEW, WIDGETS, BACKGROUND, BASE_OVERVIEW
+ public enum ContainerType {
+ WORKSPACE, ALL_APPS, OVERVIEW, WIDGETS, BACKGROUND, FALLBACK_OVERVIEW
}
public enum NavigationModel {ZERO_BUTTON, TWO_BUTTON, THREE_BUTTON}
@@ -122,7 +128,7 @@
private static final String APPS_RES_ID = "apps_view";
private static final String OVERVIEW_RES_ID = "overview_panel";
private static final String WIDGETS_RES_ID = "widgets_list_view";
- public static final int WAIT_TIME_MS = 60000;
+ public static final int WAIT_TIME_MS = 10000;
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null);
@@ -132,10 +138,22 @@
private int mExpectedRotation = Surface.ROTATION_0;
private final Uri mTestProviderUri;
private final Deque<String> mDiagnosticContext = new LinkedList<>();
+ private Function<Long, String> mSystemHealthSupplier;
+
+ private Consumer<ContainerType> mOnSettledStateAction;
/**
* Constructs the root of TAPL hierarchy. You get all other objects from it.
*/
+ public LauncherInstrumentation() {
+ this(InstrumentationRegistry.getInstrumentation());
+ }
+
+ /**
+ * Constructs the root of TAPL hierarchy. You get all other objects from it.
+ * Deprecated: use the constructor without parameters instead.
+ */
+ @Deprecated
public LauncherInstrumentation(Instrumentation instrumentation) {
mInstrumentation = instrumentation;
mDevice = UiDevice.getInstance(instrumentation);
@@ -173,6 +191,7 @@
PackageManager pm = getContext().getPackageManager();
ProviderInfo pi = pm.resolveContentProvider(
testProviderAuthority, MATCH_ALL | MATCH_DISABLED_COMPONENTS);
+ assertNotNull("Cannot find content provider for " + testProviderAuthority, pi);
ComponentName cn = new ComponentName(pi.packageName, pi.name);
if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) {
@@ -206,7 +225,7 @@
try {
// Workaround, use constructed context because both the instrumentation context and the
// app context are not constructed with resources that take overlays into account
- final Context ctx = baseContext.createPackageContext("android", 0);
+ final Context ctx = baseContext.createPackageContext(getLauncherPackageName(), 0);
for (int i = 0; i < 100; ++i) {
final int currentInteractionMode = getCurrentInteractionMode(ctx);
final NavigationModel model = getNavigationModel(currentInteractionMode);
@@ -232,10 +251,6 @@
return null;
}
- public static boolean isAvd() {
- return Build.MODEL.contains("Cuttlefish");
- }
-
static void log(String message) {
Log.d(TAG, message);
}
@@ -263,10 +278,79 @@
}
}
+ private String getAnomalyMessage() {
+ UiObject2 object = mDevice.findObject(By.res("android", "alertTitle"));
+ if (object != null) {
+ return "System alert popup is visible: " + object.getText();
+ }
+
+ object = mDevice.findObject(By.res("android", "message"));
+ if (object != null) {
+ return "Message popup by " + object.getApplicationPackage() + " is visible: "
+ + object.getText();
+ }
+
+ if (hasSystemUiObject("keyguard_status_view")) return "Phone is locked";
+
+ if (!mDevice.hasObject(By.textStartsWith(""))) return "Screen is empty";
+
+ return null;
+ }
+
+ private String getVisibleStateMessage() {
+ if (hasLauncherObject(WIDGETS_RES_ID)) return "Widgets";
+ if (hasLauncherObject(OVERVIEW_RES_ID)) return "Overview";
+ if (hasLauncherObject(WORKSPACE_RES_ID)) return "Workspace";
+ if (hasLauncherObject(APPS_RES_ID)) return "AllApps";
+ return "Background";
+ }
+
+ public void setSystemHealthSupplier(Function<Long, String> supplier) {
+ this.mSystemHealthSupplier = supplier;
+ }
+
+ public void setOnSettledStateAction(Consumer<ContainerType> onSettledStateAction) {
+ mOnSettledStateAction = onSettledStateAction;
+ }
+
+ private String getSystemHealthMessage() {
+ final String testPackage = getContext().getPackageName();
+ try {
+ mDevice.executeShellCommand("pm grant " + testPackage +
+ " android.permission.READ_LOGS");
+ mDevice.executeShellCommand("pm grant " + testPackage +
+ " android.permission.PACKAGE_USAGE_STATS");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return mSystemHealthSupplier != null
+ ? mSystemHealthSupplier.apply(START_TIME)
+ : TestHelpers.getSystemHealthMessage(getContext(), START_TIME);
+ }
+
private void fail(String message) {
- log("Hierarchy dump for: " + getContextDescription() + message);
+ message = "http://go/tapl : " + getContextDescription() + message;
+
+ final String anomaly = getAnomalyMessage();
+ if (anomaly != null) {
+ message = anomaly + ", which causes:\n" + message;
+ } else {
+ message = message + " (visible state: " + getVisibleStateMessage() + ")";
+ }
+
+ final String systemHealth = getSystemHealthMessage();
+ if (systemHealth != null) {
+ message = message
+ + ", which might be a consequence of system health "
+ + "problems:\n<<<<<<<<<<<<<<<<<<\n"
+ + systemHealth + "\n>>>>>>>>>>>>>>>>>>";
+ }
+
+ log("Hierarchy dump for: " + message);
dumpViewHierarchy();
- Assert.fail("http://go/tapl : " + getContextDescription() + message);
+
+ Assert.fail(message);
}
private String getContextDescription() {
@@ -293,7 +377,7 @@
}
}
- private void assertEquals(String message, String expected, String actual) {
+ void assertEquals(String message, String expected, String actual) {
if (!TextUtils.equals(expected, actual)) {
fail(message + " expected: '" + expected + "' but was: '" + actual + "'");
}
@@ -331,12 +415,33 @@
}
private UiObject2 verifyContainerType(ContainerType containerType) {
+ waitForLauncherInitialized();
+
assertEquals("Unexpected display rotation",
mExpectedRotation, mDevice.getDisplayRotation());
+
+ // b/136278866
+ for (int i = 0; i != 100; ++i) {
+ if (getNavigationModeMismatchError() == null) break;
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
final String error = getNavigationModeMismatchError();
assertTrue(error, error == null);
log("verifyContainerType: " + containerType);
+ final UiObject2 container = verifyVisibleObjects(containerType);
+
+ if (mOnSettledStateAction != null) mOnSettledStateAction.accept(containerType);
+
+ return container;
+ }
+
+ private UiObject2 verifyVisibleObjects(ContainerType containerType) {
try (Closable c = addContextLayer(
"but the current state is not " + containerType.name())) {
switch (containerType) {
@@ -373,7 +478,7 @@
return waitForLauncherObject(OVERVIEW_RES_ID);
}
- case BASE_OVERVIEW: {
+ case FALLBACK_OVERVIEW: {
return waitForFallbackLauncherObject(OVERVIEW_RES_ID);
}
case BACKGROUND: {
@@ -390,6 +495,18 @@
}
}
+ private void waitForLauncherInitialized() {
+ for (int i = 0; i < 100; ++i) {
+ if (getTestInfo(
+ TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED).
+ getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD)) {
+ return;
+ }
+ SystemClock.sleep(100);
+ }
+ fail("Launcher didn't initialize");
+ }
+
Parcelable executeAndWaitForEvent(Runnable command,
UiAutomation.AccessibilityEventFilter eventFilter, String message) {
try {
@@ -426,6 +543,9 @@
// accessibility events prior to pressing Home.
final String action;
if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
+ final String anomaly = getAnomalyMessage();
+ if (anomaly != null) fail("Can't swipe up to Home: " + anomaly);
+
final Point displaySize = getRealDisplaySize();
if (hasLauncherObject("deep_shortcuts_container")) {
@@ -433,13 +553,17 @@
displaySize.x / 2, displaySize.y - 1,
displaySize.x / 2, 0,
ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME);
- assertTrue("Context menu is still visible afterswiping up to home",
- !hasLauncherObject("deep_shortcuts_container"));
+ try (LauncherInstrumentation.Closable c = addContextLayer(
+ "Swiped up from context menu to home")) {
+ waitUntilGone("deep_shortcuts_container");
+ }
}
if (hasLauncherObject(WORKSPACE_RES_ID)) {
log(action = "already at home");
} else {
- log(action = "swiping up to home");
+ log("Hierarchy before swiping up to home");
+ dumpViewHierarchy();
+ log(action = "swiping up to home from " + getVisibleStateMessage());
final int finalState = mDevice.hasObject(By.pkg(getLauncherPackageName()))
? NORMAL_STATE_ORDINAL : BACKGROUND_APP_STATE_ORDINAL;
@@ -571,13 +695,6 @@
}
@NonNull
- UiObject2 getObjectInContainer(UiObject2 container, BySelector selector) {
- final UiObject2 object = container.findObject(selector);
- assertNotNull("Can't find an object with selector: " + selector, object);
- return object;
- }
-
- @NonNull
List<UiObject2> getObjectsInContainer(UiObject2 container, String resName) {
return container.findObjects(getLauncherObjectSelector(resName));
}
@@ -614,12 +731,12 @@
@NonNull
UiObject2 waitForLauncherObject(BySelector selector) {
- return waitForObjectBySelector(selector.pkg(getLauncherPackageName()));
+ return waitForObjectBySelector(By.copy(selector).pkg(getLauncherPackageName()));
}
@NonNull
UiObject2 tryWaitForLauncherObject(BySelector selector, long timeout) {
- return tryWaitForObjectBySelector(selector.pkg(getLauncherPackageName()), timeout);
+ return tryWaitForObjectBySelector(By.copy(selector).pkg(getLauncherPackageName()), timeout);
}
@NonNull
@@ -649,6 +766,10 @@
return mDevice.getLauncherPackageName();
}
+ boolean isFallbackOverview() {
+ return !getOverviewPackageName().equals(getLauncherPackageName());
+ }
+
@NonNull
public UiDevice getDevice() {
return mDevice;
@@ -658,14 +779,42 @@
final Bundle parcel = (Bundle) executeAndWaitForEvent(
() -> linearGesture(startX, startY, endX, endY, steps),
event -> TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName()),
- "Swipe failed to receive an event for the swipe end: " + startX + ", " + startY
- + ", " + endX + ", " + endY);
+ "Swipe failed to receive an event for the swipe end");
assertEquals("Swipe switched launcher to a wrong state;",
TestProtocol.stateOrdinalToString(expectedState),
TestProtocol.stateOrdinalToString(parcel.getInt(TestProtocol.STATE_FIELD)));
}
- void scroll(UiObject2 container, Direction direction, float percent, Rect margins, int steps) {
+ int getBottomGestureSize() {
+ return ResourceUtils.getNavbarSize(
+ ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, getResources()) + 1;
+ }
+
+ int getBottomGestureMargin(UiObject2 container) {
+ return container.getVisibleBounds().bottom - getRealDisplaySize().y +
+ getBottomGestureSize();
+ }
+
+ void scrollToLastVisibleRow(UiObject2 container, Collection<UiObject2> items, int topPadding) {
+ final UiObject2 lowestItem = Collections.max(items, (i1, i2) ->
+ Integer.compare(i1.getVisibleBounds().top, i2.getVisibleBounds().top));
+
+ final int gestureStart = lowestItem.getVisibleBounds().top + getTouchSlop();
+ final int distance = gestureStart - container.getVisibleBounds().top - topPadding;
+ final int bottomMargin = container.getVisibleBounds().height() - distance;
+
+ scroll(
+ container,
+ Direction.DOWN,
+ new Rect(
+ 0,
+ 0,
+ 0,
+ Math.max(bottomMargin, getBottomGestureMargin(container))),
+ 150);
+ }
+
+ void scroll(UiObject2 container, Direction direction, Rect margins, int steps) {
final Rect rect = container.getVisibleBounds();
if (margins != null) {
rect.left += margins.left;
@@ -683,19 +832,35 @@
case UP: {
startX = endX = rect.centerX();
final int vertCenter = rect.centerY();
- final float halfGestureHeight = rect.height() * percent / 2.0f;
- startY = (int) (vertCenter - halfGestureHeight);
+ final float halfGestureHeight = rect.height() / 2.0f;
+ startY = (int) (vertCenter - halfGestureHeight) + 1;
endY = (int) (vertCenter + halfGestureHeight);
}
break;
case DOWN: {
startX = endX = rect.centerX();
final int vertCenter = rect.centerY();
- final float halfGestureHeight = rect.height() * percent / 2.0f;
- startY = (int) (vertCenter + halfGestureHeight);
+ final float halfGestureHeight = rect.height() / 2.0f;
+ startY = (int) (vertCenter + halfGestureHeight) - 1;
endY = (int) (vertCenter - halfGestureHeight);
}
break;
+ case LEFT: {
+ startY = endY = rect.centerY();
+ final int horizCenter = rect.centerX();
+ final float halfGestureWidth = rect.width() / 2.0f;
+ startX = (int) (horizCenter - halfGestureWidth) + 1;
+ endX = (int) (horizCenter + halfGestureWidth);
+ }
+ break;
+ case RIGHT: {
+ startY = endY = rect.centerY();
+ final int horizCenter = rect.centerX();
+ final float halfGestureWidth = rect.width() / 2.0f;
+ startX = (int) (horizCenter + halfGestureWidth) - 1;
+ endX = (int) (horizCenter - halfGestureWidth);
+ }
+ break;
default:
fail("Unsupported direction");
return;
@@ -711,6 +876,7 @@
// Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a
// fixed interval each time.
void linearGesture(int startX, int startY, int endX, int endY, int steps) {
+ log("linearGesture: " + startX + ", " + startY + " -> " + endX + ", " + endY);
final long downTime = SystemClock.uptimeMillis();
final Point start = new Point(startX, startY);
final Point end = new Point(endX, endY);
@@ -723,10 +889,6 @@
mDevice.waitForIdle();
}
- float getDisplayDensity() {
- return mInstrumentation.getTargetContext().getResources().getDisplayMetrics().density;
- }
-
int getTouchSlop() {
return ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
@@ -760,6 +922,7 @@
}
long movePointer(long downTime, long startTime, long duration, Point from, Point to) {
+ log("movePointer: " + from + " to " + to);
final Point point = new Point();
long steps = duration / GESTURE_STEP_MS;
long currentTime = startTime;
@@ -812,7 +975,7 @@
int getEdgeSensitivityWidth() {
try {
final Context context = mInstrumentation.getTargetContext().createPackageContext(
- "android", 0);
+ getLauncherPackageName(), 0);
return context.getResources().getDimensionPixelSize(
getSystemDimensionResId(context, "config_backGestureInset")) + 1;
} catch (PackageManager.NameNotFoundException e) {
@@ -826,4 +989,25 @@
getContext().getSystemService(WindowManager.class).getDefaultDisplay().getRealSize(size);
return size;
}
+
+ public void enableDebugTracing() {
+ getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
+ }
+
+ public void disableDebugTracing() {
+ getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
+ }
+
+ public int getTotalPssKb() {
+ return getTestInfo(TestProtocol.REQUEST_TOTAL_PSS_KB).
+ getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ }
+
+ public void produceJavaLeak() {
+ getTestInfo(TestProtocol.REQUEST_JAVA_LEAK);
+ }
+
+ public void produceNativeLeak() {
+ getTestInfo(TestProtocol.REQUEST_NATIVE_LEAK);
+ }
}
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Overview.java b/tests/tapl/com/android/launcher3/tapl/Overview.java
index 058831f..da68da3 100644
--- a/tests/tapl/com/android/launcher3/tapl/Overview.java
+++ b/tests/tapl/com/android/launcher3/tapl/Overview.java
@@ -19,9 +19,9 @@
import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
import androidx.annotation.NonNull;
-import androidx.test.uiautomator.UiObject2;
import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
+import com.android.launcher3.testing.TestProtocol;
/**
* Overview pane.
@@ -51,11 +51,15 @@
// Swipe from an app icon to the top.
LauncherInstrumentation.log("Overview.switchToAllApps before swipe");
- final UiObject2 allApps = mLauncher.waitForLauncherObject("apps_view");
- mLauncher.swipeToState(mLauncher.getDevice().getDisplayWidth() / 2,
- allApps.getVisibleBounds().top,
+ mLauncher.swipeToState(
mLauncher.getDevice().getDisplayWidth() / 2,
- 0, 50, ALL_APPS_STATE_ORDINAL);
+ mLauncher.getTestInfo(
+ TestProtocol.REQUEST_HOTSEAT_TOP).
+ getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD),
+ mLauncher.getDevice().getDisplayWidth() / 2,
+ 0,
+ 50,
+ ALL_APPS_STATE_ORDINAL);
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
"swiped all way up from overview")) {
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 641c413..91f0fc4 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -16,18 +16,16 @@
package com.android.launcher3.tapl;
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+
import android.graphics.Rect;
import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
-
-import com.android.launcher3.testing.TestProtocol;
/**
* A recent task in the overview panel carousel.
*/
public final class OverviewTask {
- private static final long WAIT_TIME_MS = 60000;
private final LauncherInstrumentation mLauncher;
private final UiObject2 mTask;
private final BaseOverview mOverview;
@@ -64,14 +62,14 @@
*/
public Background open() {
verifyActiveContainer();
- mLauncher.getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"clicking an overview task")) {
- mLauncher.assertTrue("Launching task didn't open a new window: " +
- mTask.getParent().getContentDescription(),
- mTask.clickAndWait(Until.newWindow(), WAIT_TIME_MS));
+ mLauncher.executeAndWaitForEvent(
+ () -> mTask.click(),
+ event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
+ "Launching task didn't open a new window: " +
+ mTask.getParent().getContentDescription());
}
- mLauncher.getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
return new Background(mLauncher);
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
index 93554d2..0c9fda3 100644
--- a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
+++ b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
@@ -26,7 +26,11 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
+import android.os.DropBoxManager;
+import org.junit.Assert;
+
+import java.util.Date;
import java.util.List;
public class TestHelpers {
@@ -81,4 +85,69 @@
}
return "com.android.systemui";
}
+
+ private static String truncateCrash(String text, int maxLines) {
+ String[] lines = text.split("\\r?\\n");
+ StringBuilder ret = new StringBuilder();
+ for (int i = 0; i < maxLines && i < lines.length; i++) {
+ ret.append(lines[i]);
+ ret.append('\n');
+ }
+ if (lines.length > maxLines) {
+ ret.append("... ");
+ ret.append(lines.length - maxLines);
+ ret.append(" more lines truncated ...\n");
+ }
+ return ret.toString();
+ }
+
+ private static String checkCrash(Context context, String label, long startTime) {
+ DropBoxManager dropbox = (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE);
+ Assert.assertNotNull("Unable access the DropBoxManager service", dropbox);
+
+ long timestamp = startTime;
+ DropBoxManager.Entry entry;
+ StringBuilder errorDetails = new StringBuilder();
+ while (null != (entry = dropbox.getNextEntry(label, timestamp))) {
+ errorDetails.append("------------------------------\n");
+ timestamp = entry.getTimeMillis();
+ errorDetails.append(new Date(timestamp));
+ errorDetails.append(": ");
+ errorDetails.append(entry.getTag());
+ errorDetails.append(": ");
+ final String dropboxSnippet = entry.getText(4096);
+ if (dropboxSnippet != null) errorDetails.append(truncateCrash(dropboxSnippet, 40));
+ errorDetails.append(" ...\n");
+ entry.close();
+ }
+ return errorDetails.length() != 0 ? errorDetails.toString() : null;
+ }
+
+ public static String getSystemHealthMessage(Context context, long startTime) {
+ try {
+ StringBuilder errors = new StringBuilder();
+
+ final String[] labels = {
+ "system_app_anr",
+ "system_app_crash",
+ "system_app_native_crash",
+ "system_server_anr",
+ "system_server_crash",
+ "system_server_native_crash",
+ "system_server_watchdog",
+ };
+
+ for (String label : labels) {
+ final String crash = checkCrash(context, label, startTime);
+ if (crash != null) errors.append(crash);
+ }
+
+ return errors.length() != 0
+ ? "Current time: " + new Date(System.currentTimeMillis()) + "\n" + errors
+ : null;
+ } catch (Exception e) {
+ return "Failed to get system health diags, maybe build your test via .bp instead of "
+ + ".mk? " + android.util.Log.getStackTraceString(e);
+ }
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Widget.java b/tests/tapl/com/android/launcher3/tapl/Widget.java
index 128789d..1b6d8c4 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widget.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widget.java
@@ -18,7 +18,16 @@
import androidx.test.uiautomator.UiObject2;
-public class Widget {
- Widget(LauncherInstrumentation launcher, UiObject2 widget) {
+/**
+ * Widget in workspace or a widget list.
+ */
+public final class Widget extends Launchable {
+ Widget(LauncherInstrumentation launcher, UiObject2 icon) {
+ super(launcher, icon);
+ }
+
+ @Override
+ protected String getLongPressIndicator() {
+ return "drop_target_bar";
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 94003be..51239c9 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -16,16 +16,21 @@
package com.android.launcher3.tapl;
+import android.graphics.Point;
+import android.graphics.Rect;
+
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiObject2;
-import com.android.launcher3.ResourceUtils;
+import java.util.Collection;
/**
* All widgets container.
*/
public final class Widgets extends LauncherInstrumentation.VisibleContainer {
- private static final int FLING_SPEED = 1500;
+ private static final int FLING_STEPS = 10;
Widgets(LauncherInstrumentation launcher) {
super(launcher);
@@ -40,11 +45,11 @@
"want to fling forward in widgets")) {
LauncherInstrumentation.log("Widgets.flingForward enter");
final UiObject2 widgetsContainer = verifyActiveContainer();
- widgetsContainer.setGestureMargins(0, 0, 0,
- ResourceUtils.getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
- mLauncher.getResources()) + 1);
- widgetsContainer.fling(Direction.DOWN,
- (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
+ mLauncher.scroll(
+ widgetsContainer,
+ Direction.DOWN,
+ new Rect(0, 0, 0, mLauncher.getBottomGestureMargin(widgetsContainer)),
+ FLING_STEPS);
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung forward")) {
verifyActiveContainer();
}
@@ -60,10 +65,11 @@
"want to fling backwards in widgets")) {
LauncherInstrumentation.log("Widgets.flingBackward enter");
final UiObject2 widgetsContainer = verifyActiveContainer();
- widgetsContainer.setGestureMargin(100);
- widgetsContainer.fling(Direction.UP,
- (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
- mLauncher.waitForIdle();
+ mLauncher.scroll(
+ widgetsContainer,
+ Direction.UP,
+ new Rect(0, 0, widgetsContainer.getVisibleBounds().width(), 0),
+ FLING_STEPS);
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung back")) {
verifyActiveContainer();
}
@@ -75,4 +81,35 @@
protected LauncherInstrumentation.ContainerType getContainerType() {
return LauncherInstrumentation.ContainerType.WIDGETS;
}
+
+ public Widget getWidget(String labelText) {
+ final UiObject2 widgetsContainer = verifyActiveContainer();
+ final Point displaySize = mLauncher.getRealDisplaySize();
+ final BySelector labelSelector = By.clazz("android.widget.TextView").text(labelText);
+
+ int i = 0;
+ for (; ; ) {
+ final Collection<UiObject2> cells = mLauncher.getObjectsInContainer(
+ widgetsContainer, "widgets_cell_list_container");
+ mLauncher.assertTrue("Widgets doesn't have 2 rows", cells.size() >= 2);
+ for (UiObject2 cell : cells) {
+ final UiObject2 label = cell.findObject(labelSelector);
+ if (label == null) continue;
+
+ final UiObject2 widget = label.getParent().getParent();
+ mLauncher.assertEquals(
+ "View is not WidgetCell",
+ "com.android.launcher3.widget.WidgetCell",
+ widget.getClassName());
+
+ if (widget.getVisibleBounds().bottom <=
+ displaySize.y - mLauncher.getBottomGestureSize()) {
+ return new Widget(mLauncher, widget);
+ }
+ }
+
+ mLauncher.assertTrue("Too many attempts", ++i <= 40);
+ mLauncher.scrollToLastVisibleRow(widgetsContainer, cells, 0);
+ }
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index b01b6f3..0aa36dd 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -21,6 +21,7 @@
import static junit.framework.TestCase.assertTrue;
import android.graphics.Point;
+import android.graphics.Rect;
import android.os.SystemClock;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -37,9 +38,8 @@
* Operations on the workspace screen.
*/
public final class Workspace extends Home {
- private static final float FLING_SPEED =
- LauncherInstrumentation.isAvd() ? 1500.0F : 3500.0F;
private static final int DRAG_DURACTION = 2000;
+ private static final int FLING_STEPS = 10;
private final UiObject2 mHotseat;
Workspace(LauncherInstrumentation launcher) {
@@ -67,7 +67,6 @@
"switchToAllApps: swipeHeight = " + swipeHeight + ", slop = "
+ mLauncher.getTouchSlop());
- mLauncher.getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
mLauncher.swipeToState(
start.x,
start.y,
@@ -75,7 +74,6 @@
start.y - swipeHeight - mLauncher.getTouchSlop(),
60,
ALL_APPS_STATE_ORDINAL);
- mLauncher.getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
"swiped to all apps")) {
@@ -110,10 +108,13 @@
*/
@NonNull
public AppIcon getWorkspaceAppIcon(String appName) {
- return new AppIcon(mLauncher,
- mLauncher.getObjectInContainer(
- verifyActiveContainer(),
- AppIcon.getAppIconSelector(appName, mLauncher)));
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to get a workspace icon")) {
+ return new AppIcon(mLauncher,
+ mLauncher.waitForObjectInContainer(
+ verifyActiveContainer(),
+ AppIcon.getAppIconSelector(appName, mLauncher)));
+ }
}
/**
@@ -139,25 +140,24 @@
}
private boolean isWorkspaceScrollable(UiObject2 workspace) {
- return workspace.isScrollable();
+ return workspace.getChildCount() > 1;
}
@NonNull
public AppIcon getHotseatAppIcon(String appName) {
- return new AppIcon(mLauncher, mLauncher.getObjectInContainer(
+ return new AppIcon(mLauncher, mLauncher.waitForObjectInContainer(
mHotseat, AppIcon.getAppIconSelector(appName, mLauncher)));
}
@NonNull
public Folder getHotseatFolder(String appName) {
- return new Folder(mLauncher, mLauncher.getObjectInContainer(
+ return new Folder(mLauncher, mLauncher.waitForObjectInContainer(
mHotseat, Folder.getSelector(appName, mLauncher)));
}
static void dragIconToWorkspace(
LauncherInstrumentation launcher, Launchable launchable, Point dest,
String longPressIndicator) {
- launcher.getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
LauncherInstrumentation.log("dragIconToWorkspace: begin");
final Point launchableCenter = launchable.getObject().getVisibleCenter();
final long downTime = SystemClock.uptimeMillis();
@@ -172,7 +172,6 @@
downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest);
LauncherInstrumentation.log("dragIconToWorkspace: end");
launcher.waitUntilGone("drop_target_bar");
- launcher.getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
}
/**
@@ -181,9 +180,9 @@
*/
public void flingForward() {
final UiObject2 workspace = verifyActiveContainer();
- workspace.setGestureMargins(0, 0, mLauncher.getEdgeSensitivityWidth(), 0);
- workspace.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
- mLauncher.waitForIdle();
+ mLauncher.scroll(workspace, Direction.RIGHT,
+ new Rect(0, 0, mLauncher.getEdgeSensitivityWidth(), 0),
+ FLING_STEPS);
verifyActiveContainer();
}
@@ -193,9 +192,9 @@
*/
public void flingBackward() {
final UiObject2 workspace = verifyActiveContainer();
- workspace.setGestureMargins(mLauncher.getEdgeSensitivityWidth(), 0, 0, 0);
- workspace.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
- mLauncher.waitForIdle();
+ mLauncher.scroll(workspace, Direction.LEFT,
+ new Rect(mLauncher.getEdgeSensitivityWidth(), 0, 0, 0),
+ FLING_STEPS);
verifyActiveContainer();
}