diff options
author | android-build-prod (mdb) <android-build-team-robot@google.com> | 2020-02-11 20:44:28 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2020-02-11 20:44:28 +0000 |
commit | 5eb9e6c0c06ff205a6aaf339ca7be958a9bf6136 (patch) | |
tree | 15adb3d205c764c6f3e0cb7dd45731c2381ac524 | |
parent | e444b7b690f794553e0322385c05d8ac4830c046 (diff) | |
parent | 9d9cf1eeb7d00f47cecb9b13b44df373a04d122f (diff) | |
download | Launcher3-platform-tools-29.0.6.tar.gz |
Merge "Snap for 6198741 from ca891c7a2200cd623562fe9f2e644c054d1842a5 to sdk-release" into sdk-releaseplatform-tools-29.0.6
216 files changed, 4922 insertions, 2544 deletions
diff --git a/Android.mk b/Android.mk index 9d113d9547..aabb43d82d 100644 --- a/Android.mk +++ b/Android.mk @@ -100,7 +100,7 @@ LOCAL_SDK_VERSION := current LOCAL_MIN_SDK_VERSION := 21 LOCAL_PACKAGE_NAME := Launcher3 LOCAL_PRIVILEGED_MODULE := true -LOCAL_PRODUCT_MODULE := true +LOCAL_SYSTEM_EXT_MODULE := true LOCAL_OVERRIDES_PACKAGES := Home Launcher2 LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3 @@ -131,7 +131,7 @@ LOCAL_SDK_VERSION := current LOCAL_MIN_SDK_VERSION := 21 LOCAL_PACKAGE_NAME := Launcher3Go LOCAL_PRIVILEGED_MODULE := true -LOCAL_PRODUCT_MODULE := true +LOCAL_SYSTEM_EXT_MODULE := true LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3 @@ -199,7 +199,7 @@ else endif LOCAL_PACKAGE_NAME := Launcher3QuickStep LOCAL_PRIVILEGED_MODULE := true -LOCAL_PRODUCT_MODULE := true +LOCAL_SYSTEM_EXT_MODULE := true LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3 @@ -250,7 +250,7 @@ LOCAL_PROGUARD_ENABLED := full LOCAL_PACKAGE_NAME := Launcher3QuickStepGo LOCAL_PRIVILEGED_MODULE := true -LOCAL_PRODUCT_MODULE := true +LOCAL_SYSTEM_EXT_MODULE := true LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep Launcher3GoIconRecents LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3 @@ -297,7 +297,7 @@ LOCAL_PROGUARD_ENABLED := full LOCAL_PACKAGE_NAME := Launcher3GoIconRecents LOCAL_PRIVILEGED_MODULE := true -LOCAL_PRODUCT_MODULE := true +LOCAL_SYSTEM_EXT_MODULE := true LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3Go Launcher3QuickStep LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3 diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml index 5318a12edd..8db875be90 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" /> <!-- diff --git a/CleanSpec.mk b/CleanSpec.mk index f58158f148..489abd1881 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -54,6 +54,16 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Launcher2_interm $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/Launcher2.apk) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/Launcher3QuickStep) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/Launcher3) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/Launcher3Go) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/Launcher3QuickStep) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/Launcher3QuickStepGo) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/Launcher3GoIconRecents) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/Launcher3) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/Launcher3Go) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/Launcher3QuickStep) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/Launcher3QuickStepGo) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/Launcher3GoIconRecents) # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST # ************************************************ @@ -10,3 +10,6 @@ mrcasey@google.com 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/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml b/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml index bf76f29dec..c02fe2cdce 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/src/com/android/launcher3/uioverrides/states/OverviewState.java b/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java index d0cfcf97a5..212ce9bef6 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_2; 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 class OverviewState extends LauncherState { } 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 1ccd7d79ac..66aec40509 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 @@ public final class LandscapeStatesTouchController extends PortraitStatesTouchCon } @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 fe159b5f02..92900f2168 100644 --- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -25,6 +25,7 @@ import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MOD 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 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> imple } @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 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> imple ); return; } - result.setAnimation(createWindowAnimation(targetCompats)); + result.setAnimation(createWindowAnimation(targetCompats), context); } }; return ActivityOptionsCompat.makeRemoteAnimation( diff --git a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java index 900b94e186..577b175661 100644 --- a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -34,6 +34,8 @@ import android.view.MotionEvent; import com.android.launcher3.Utilities; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.util.LooperExecutor; +import com.android.launcher3.util.UiThreadHelper; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; @@ -137,6 +139,9 @@ public class TouchInteractionService extends Service { return sConnected; } + public static final LooperExecutor BACKGROUND_EXECUTOR = + new LooperExecutor(UiThreadHelper.getBackgroundLooper()); + private RecentsModel mRecentsModel; private OverviewComponentObserver mOverviewComponentObserver; private OverviewCommandHelper mOverviewCommandHelper; @@ -180,4 +185,8 @@ public class TouchInteractionService extends Service { } return mMyBinder; } + + public static boolean isInitialized() { + return true; + } } diff --git a/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java index 1e449108d8..ee113dfebf 100644 --- a/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java +++ b/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java @@ -25,6 +25,7 @@ import android.os.Bundle; import android.os.UserHandle; import com.android.launcher3.ItemInfo; +import com.android.launcher3.notification.NotificationKeyData; import java.util.Collections; import java.util.List; @@ -48,10 +49,6 @@ public class DeepShortcutManager { private DeepShortcutManager(Context context) { } - public static boolean supportsShortcuts(ItemInfo info) { - return false; - } - public boolean wasLastCallSuccess() { return false; } diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java index 60320d63be..e1b71a0b5f 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java +++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java @@ -22,6 +22,7 @@ import android.graphics.drawable.Drawable; import android.os.Build; 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 @@ -154,7 +155,7 @@ public class BaseIconFactory implements AutoCloseable { * @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 +205,11 @@ public class BaseIconFactory implements AutoCloseable { 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) { @@ -261,7 +265,7 @@ public class BaseIconFactory implements AutoCloseable { * @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; diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java index d84633d563..36d1c3eccc 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.BitmapFactory; 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.Map; 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 @@ public abstract class BaseIconCache { protected int mIconDpi; protected IconDB mIconDb; + protected LocaleList mLocaleList = LocaleList.getEmptyLocaleList(); protected String mSystemState = ""; private final String mDbFileName; @@ -227,12 +230,12 @@ public abstract class BaseIconCache { /** * 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) { @@ -269,7 +272,7 @@ public abstract class BaseIconCache { mCache.put(key, entry); ContentValues values = newContentValues(entry, entry.title.toString(), - componentName.getPackageName()); + componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList)); addIconToDB(values, componentName, info, userSerial); } @@ -445,7 +448,7 @@ public abstract class BaseIconCache { // 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 +507,35 @@ public abstract class BaseIconCache { return false; } - static final class IconDB extends SQLiteCacheHelper { - private final static int RELEASE_VERSION = 26; - - 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"; - - public final static String[] COLUMNS_HIGH_RES = new String[] { + /** + * 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); + } + + /** + * Cache class to store the actual entries on disk + */ + public static final class IconDB extends SQLiteCacheHelper { + private static final int RELEASE_VERSION = 27; + + 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 +544,23 @@ public abstract class BaseIconCache { @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 +568,7 @@ public abstract class BaseIconCache { 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 addb51fa78..09f59b84c2 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 @@ package com.android.launcher3.icons.cache; 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,12 @@ public interface CachingLogic<T> { 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; + } } diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml index 332e0fa360..546548036e 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" 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 371161ebdc..711594386f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java @@ -18,16 +18,11 @@ package com.android.launcher3; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; import static com.android.launcher3.LauncherState.NORMAL; -import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; import static com.android.launcher3.anim.Interpolators.LINEAR; 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; @@ -35,18 +30,17 @@ import android.animation.ObjectAnimator; import android.content.Context; import android.view.View; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.AnimatorPlaybackController; 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; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + /** * A {@link QuickstepAppTransitionManagerImpl} that also implements recents transitions from * {@link RecentsView}. @@ -156,8 +150,11 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti 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); 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 311db21935..425fb13990 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.util.AttributeSet; 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.allapps.FloatingHeaderView; 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 @@ public class AppsDividerView extends View implements LauncherStateManager.StateL } @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/PredictionAppTracker.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java index 8f1282dedc..24fc61bef9 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 @@ -95,6 +95,10 @@ public class PredictionAppTracker extends AppLaunchTracker { 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) 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 cb5cbddd4a..0c7ba9c95d 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.View; 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; @@ -62,9 +65,6 @@ import java.util.ArrayList; 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 +80,7 @@ public class PredictionRowView extends LinearLayout implements @Override public Integer get(PredictionRowView view) { - return view.mIconCurrentTextAlpha; + return view.mIconLastSetTextAlpha; } }; @@ -103,6 +103,8 @@ public class PredictionRowView extends LinearLayout implements 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; @@ -315,8 +317,14 @@ public class PredictionRowView extends LinearLayout implements } } - 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 +332,13 @@ public class PredictionRowView extends LinearLayout implements } @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 +366,15 @@ public class PredictionRowView extends LinearLayout implements } @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 085bbc4a5e..1a59770a02 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,7 +24,6 @@ import android.app.prediction.AppPredictor; import android.app.prediction.AppTarget; import android.content.ComponentName; import android.content.Context; -import android.view.ViewTreeObserver.OnGlobalLayoutListener; import com.android.launcher3.AppInfo; import com.android.launcher3.InvariantDeviceProfile; @@ -32,6 +31,8 @@ import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; +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; @@ -58,7 +59,7 @@ import java.util.List; * 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 +154,10 @@ public class PredictionUiStateManager implements OnGlobalLayoutListener, ItemInf 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 +166,8 @@ public class PredictionUiStateManager implements OnGlobalLayoutListener, ItemInf mPendingState = null; } if (mPendingState == null) { - mAppsView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + Launcher.getLauncher(mAppsView.getContext()).getStateManager() + .removeStateListener(this); } } @@ -170,9 +175,8 @@ public class PredictionUiStateManager implements OnGlobalLayoutListener, ItemInf 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); } } 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 a3c2e3cc81..596bc4f44c 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 @@ -164,6 +164,11 @@ public abstract class RecentsUiFactory { } } + 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 5ee08c12d2..50cfac8e43 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 @@ -19,6 +19,7 @@ import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; +import com.android.launcher3.Utilities; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.quickstep.util.LayoutUtils; @@ -68,8 +69,8 @@ public class BackgroundAppState extends OverviewState { if (taskCount == 0) { return super.getOverviewScaleAndTranslation(launcher); } - TaskView dummyTask = recentsView.getTaskViewAt(Math.max(taskCount - 1, - recentsView.getCurrentPage())); + TaskView dummyTask = recentsView.getTaskViewAt(Utilities.boundToRange( + recentsView.getCurrentPage(), 0, taskCount - 1)); return recentsView.getTempClipAnimationHelper().updateForFullscreenOverview(dummyTask) .getScaleAndTranslation(); } 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 c954762837..427206a65b 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 @@ public class OverviewPeekState extends OverviewState { 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 5543860eec..93d4de17de 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 @@ -32,6 +32,7 @@ import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7; import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE; +import android.content.Context; import android.graphics.Rect; import android.view.View; @@ -43,7 +44,6 @@ import com.android.launcher3.R; 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; @@ -128,14 +128,15 @@ public class OverviewState extends LauncherState { 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 +160,15 @@ public class OverviewState extends LauncherState { } public static float getDefaultSwipeHeight(Launcher launcher) { - return getDefaultSwipeHeight(launcher.getDeviceProfile()); + return getDefaultSwipeHeight(launcher, launcher.getDeviceProfile()); } - public static float getDefaultSwipeHeight(DeviceProfile dp) { - return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx; + 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; } @Override 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 3fe4bcfd9c..ab346c0599 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 @@ -23,13 +23,17 @@ 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_ALL_APPS_FADE; +import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_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.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_3; +import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; @@ -43,6 +47,7 @@ import android.view.ViewConfiguration; import com.android.launcher3.Launcher; 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.quickstep.OverviewInteractionState; import com.android.quickstep.util.MotionPauseDetector; @@ -102,6 +107,9 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { mPeekAnim.start(); recentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + + mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(isPaused ? 0 : 1, + peekDuration, 0); }); } } @@ -120,6 +128,13 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { 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(LINEAR, + progressToReachOverview, 1)); // Get workspace out of the way quickly, to prepare for potential pause. builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL_3); @@ -168,6 +183,21 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { } @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, 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 73f328bc1d..90911684ec 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 @@ public class OverviewToAllAppsTouchController extends PortraitStatesTouchControl } @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/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java index 18b8af4fa7..eb571f607a 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 @@ -154,7 +154,7 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll } @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 8e32bb370e..00e4f58e92 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,10 @@ 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.util.DefaultDisplay.getSingleFrameMs; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -266,8 +266,8 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity> 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/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java index 5e77e0adee..ad90e1686e 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 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> imple }); factory.onRemoteAnimationReceived(null); factory.createActivityController(RECENTS_LAUNCH_DURATION); + factory.setRecentsAttachedToAppWindow(true, false); mActivity = activity; mRecentsView = mActivity.getOverviewPanel(); return false; @@ -101,6 +102,7 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> imple 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 0000000000..d627a7f14f --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java @@ -0,0 +1,509 @@ +/* + * 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 android.os.VibrationEffect.EFFECT_CLICK; +import static android.os.VibrationEffect.createPredefined; + +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.views.FloatingIconView.SHAPE_PROGRESS_DURATION; +import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR; +import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR; +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.os.VibrationEffect; +import android.os.Vibrator; +import android.provider.Settings; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.animation.Interpolator; + +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.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; + +import androidx.annotation.UiThread; + +/** + * 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(); + + private final Vibrator mVibrator; + 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_THREAD_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); + mVibrator = context.getSystemService(Vibrator.class); + 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() { + if (!mVibrator.hasVibrator()) { + return; + } + if (Settings.System.getInt( + mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) == 0) { + return; + } + + VibrationEffect effect = createPredefined(EFFECT_CLICK); + if (effect == null) { + return; + } + BACKGROUND_EXECUTOR.execute(() -> mVibrator.vibrate(effect)); + } + + 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.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 -> { + 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.getSystemService(WindowManager.class)); + 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; + // 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(progress) + .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 c43155b73a..8c5a78823a 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 @@ public final class FallbackActivityControllerHelper implements @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 b2a71a4882..36eb8a13bd 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -40,7 +40,6 @@ import android.graphics.Rect; 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; @@ -58,7 +57,6 @@ import com.android.launcher3.LauncherStateManager; 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; @@ -151,16 +149,10 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe @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); } } @@ -194,9 +186,6 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe @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,9 +200,6 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe // 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 boolean mIsAttachedToWindow; @@ -395,6 +381,10 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe TaskView runningTaskView = recentsView.getRunningTaskView(); if (runningTaskView == null) { runningTaskView = recentsView.getTaskViewAt(recentsView.getCurrentPage()); + 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(); 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 6533c63efa..14ff47b6a9 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java @@ -25,11 +25,13 @@ import android.annotation.TargetApi; 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; @@ -99,6 +101,7 @@ public class OverviewCommandHelper { @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) { @@ -161,6 +164,9 @@ public class OverviewCommandHelper { @Override public void run() { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.ALL_APPS_UPON_RECENTS, "RecentsActivityCommand.run"); + } long elapsedTime = mCreateTime - mLastToggleTime; mLastToggleTime = mCreateTime; 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 0000000000..da4642636d --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java @@ -0,0 +1,83 @@ +package com.android.quickstep; + +import android.content.Context; +import android.os.Bundle; + +import com.android.launcher3.MainThreadExecutor; +import com.android.launcher3.testing.TestInformationHandler; +import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.uioverrides.states.OverviewState; +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 = + OverviewState.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_IS_LAUNCHER_INITIALIZED: { + response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, + TouchInteractionService.isInitialized()); + 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 = new MainThreadExecutor().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 = new MainThreadExecutor().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); + } +} 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 fc29a5663b..f08ae4a827 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.Animator; 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.launcher3.views.BaseDragLayer; 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 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat; */ 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 @@ public final class RecentsActivity extends BaseRecentsActivity { } @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(); @@ -132,7 +155,7 @@ public final class RecentsActivity extends BaseRecentsActivity { mFallbackRecentsView.resetViewUI(); } }); - result.setAnimation(anim); + result.setAnimation(anim, RecentsActivity.this); } }; return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat( @@ -178,6 +201,12 @@ public final class RecentsActivity extends BaseRecentsActivity { 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 ddd28a3500..c4d3fa03aa 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_CANCEL; 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 com.android.systemui.shared.system.InputConsumerController; 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 @@ public class RecentsAnimationWrapper { boolean sendUserLeaveHint) { SwipeAnimationTargetSet controller = targetSet; targetSet = null; + disableInputProxy(); if (controller != null) { controller.finishController(toRecents, onFinishComplete, sendUserLeaveHint); } @@ -155,6 +158,16 @@ public class RecentsAnimationWrapper { 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 @@ public class RecentsAnimationWrapper { 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 @@ public class RecentsAnimationWrapper { } } 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/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java index b90f6c2b17..17457aace6 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 class TaskOverlayFactory implements ResourceBasedOverride { }; 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 213c5d3244..fd45923070 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java @@ -207,8 +207,7 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut } }; WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture( - future, animStartedListener, mHandler, true /* scaleUp */, - v.getDisplay().getDisplayId()); + future, animStartedListener, mHandler, true /* scaleUp */, displayId); } }); } 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 53da0f92dd..86ba855784 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -31,18 +31,22 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_H 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; import android.app.ActivityManager.RunningTaskInfo; import android.app.Service; +import android.app.TaskInfo; import android.content.BroadcastReceiver; import android.content.ComponentName; 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; @@ -65,6 +69,7 @@ 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; @@ -74,6 +79,9 @@ import com.android.launcher3.Utilities; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.logging.EventLogArray; import com.android.launcher3.logging.UserEventDispatcher; +import com.android.launcher3.model.AppLaunchTracker; +import com.android.launcher3.provider.RestoreDbTask; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.UiThreadHelper; import com.android.quickstep.SysUINavigationMode.Mode; @@ -96,8 +104,10 @@ import com.android.systemui.shared.system.InputConsumerController; 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 com.android.systemui.shared.system.TaskInfoCompat; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Arrays; @@ -119,10 +129,6 @@ class ArgList extends LinkedList<String> { public String nextArg() { return pollFirst().toLowerCase(); } - - public String nextArgExact() { - return pollFirst(); - } } /** @@ -141,6 +147,11 @@ public class TouchInteractionService extends Service implements 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) { @@ -152,6 +163,8 @@ public class TouchInteractionService extends Service implements .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY)); MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor); MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet); + MAIN_THREAD_EXECUTOR.execute(() -> preloadOverview(true /* fromInit */)); + sIsInitialized = true; } @Override @@ -199,6 +212,10 @@ public class TouchInteractionService extends Service implements mOverviewComponentObserver.getActivityControlHelper(); UserEventDispatcher.newInstance(getBaseContext()).logActionBack(completed, downX, downY, isButton, gestureSwipeLeft, activityControl.getContainerType()); + + if (completed && !isButton && shouldNotifyBackGesture()) { + BACKGROUND_EXECUTOR.execute(TouchInteractionService.this::tryNotifyBackGesture); + } } public void onSystemUiStateChanged(int stateFlags) { @@ -225,12 +242,17 @@ public class TouchInteractionService extends Service implements }; private static boolean sConnected = false; + private static boolean sIsInitialized = false; private static final SwipeSharedState sSwipeSharedState = new SwipeSharedState(); public static boolean isConnected() { return sConnected; } + public static boolean isInitialized() { + return sIsInitialized; + } + public static SwipeSharedState getSwipeSharedState() { return sSwipeSharedState; } @@ -238,6 +260,11 @@ public class TouchInteractionService extends Service implements 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; @@ -319,6 +346,9 @@ public class TouchInteractionService extends Service implements 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 +357,25 @@ public class TouchInteractionService extends Service implements } 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); } @@ -394,6 +433,9 @@ public class TouchInteractionService extends Service implements @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( @@ -448,6 +490,8 @@ public class TouchInteractionService extends Service implements // 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,6 +522,7 @@ public class TouchInteractionService extends Service implements @Override public void onDestroy() { + sIsInitialized = false; if (mIsUserUnlocked) { mInputConsumer.unregisterInputConsumer(); mOverviewComponentObserver.onDestroy(); @@ -502,6 +547,9 @@ public class TouchInteractionService extends Service implements } 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; @@ -534,6 +582,7 @@ public class TouchInteractionService extends Service implements 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 @@ public class TouchInteractionService extends Service implements 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; } @@ -591,7 +640,7 @@ public class TouchInteractionService extends Service implements } 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 +652,19 @@ public class TouchInteractionService extends Service implements final ActivityControlHelper activityControl = mOverviewComponentObserver.getActivityControlHelper(); + boolean forceOverviewInputConsumer = false; + if (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); + if (!ActivityManagerWrapper.isHomeTask(runningTaskInfo)) { + final ComponentName homeComponent = + mOverviewComponentObserver.getHomeIntent().getComponent(); + forceOverviewInputConsumer = + runningTaskInfo.baseIntent.getComponent().equals(homeComponent); + } + } + if (runningTaskInfo == null && !sSwipeSharedState.goingToLauncher && !sSwipeSharedState.recentsAnimationFinishInterrupted) { return mResetGestureInputConsumer; @@ -612,22 +674,25 @@ public class TouchInteractionService extends Service implements RunningTaskInfo info = new ActivityManager.RunningTaskInfo(); info.id = sSwipeSharedState.nextRunningTaskId; return createOtherActivityInputConsumer(event, info); - } else if (sSwipeSharedState.goingToLauncher || activityControl.isResumed()) { + } else if (sSwipeSharedState.goingToLauncher || activityControl.isResumed() + || forceOverviewInputConsumer) { return createOverviewInputConsumer(event); } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityControl.isInLiveTileMode()) { return createOverviewInputConsumer(event); } 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); } } + private boolean isExcludedAssistant(TaskInfo info) { + return info != null + && TaskInfoCompat.getActivityType(info) == ACTIVITY_TYPE_ASSISTANT + && (info.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0; + } + private boolean disableHorizontalSwipe(MotionEvent event) { // mExclusionRegion can change on binder thread, use a local instance here. Region exclusionRegion = mExclusionRegion; @@ -635,17 +700,25 @@ public class TouchInteractionService extends Service implements && 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); } private InputConsumer createDeviceLockedInputConsumer(RunningTaskInfo taskInfo) { @@ -669,7 +742,7 @@ public class TouchInteractionService extends Service implements return new OverviewInputConsumer(activity, mInputMonitorCompat, false /* startingInActivityBounds */); } else { - return new OverviewWithoutFocusInputConsumer(this, mInputMonitorCompat, + return new OverviewWithoutFocusInputConsumer(activity, mInputMonitorCompat, disableHorizontalSwipe(event)); } } @@ -684,6 +757,60 @@ public class TouchInteractionService extends Service implements } } + 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 +835,9 @@ public class TouchInteractionService extends Service implements 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 +867,37 @@ public class TouchInteractionService extends Service implements 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) { + BACKGROUND_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 f790c4027d..e1085e6086 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -17,22 +17,18 @@ package com.android.quickstep; 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; @@ -43,65 +39,49 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_O 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.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 +89,14 @@ import com.android.systemui.shared.recents.model.ThumbnailData; 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) { @@ -220,64 +195,31 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> 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 +228,17 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> 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 +301,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> } } - 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 +323,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> } 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 +337,23 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> 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 +363,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> // 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,30 +440,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> 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); } @@ -651,6 +491,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate); } + @Override public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) { mIsLikelyToStartNewTask = isLikelyToStartNewTask; @@ -666,9 +507,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> 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 +537,18 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> 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 +561,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> 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(); } } @@ -767,39 +605,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> @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,7 +620,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> TOUCH_INTERACTION_LOG.addLog("cancelRecentsAnimation"); } - @UiThread + @Override public void onGestureStarted() { notifyGestureStartedAsync(); mShiftAtGestureStart = mCurrentShift.value; @@ -838,7 +644,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> /** * 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 +657,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> * @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 +675,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> 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 +689,13 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> ? 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 +781,14 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> 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 +923,15 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> 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 +970,22 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> * @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 +997,18 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> 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 +1021,18 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> @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 +1040,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> * 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 +1063,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> } private void invalidateHandler() { - endRunningWindowAnim(); + endRunningWindowAnim(false /* cancel */); if (mGestureEndCallback != null) { mGestureEndCallback.run(); @@ -1322,7 +1079,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> mRecentsView.onGestureAnimationEnd(); mActivity.getRootView().setOnApplyWindowInsetsListener(null); - mActivity.getRootView().getOverlay().remove(mLiveTileOverlay); + removeLiveTileOverlay(); } private void endLauncherTransitionController() { @@ -1437,7 +1194,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> 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 +1204,28 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> 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 +1236,4 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> 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 c28761804b..2e9c0a3c4d 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 @@ package com.android.quickstep.fallback; 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.RecentsActivity; 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 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity> { 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 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity> { } @Override + public void reset() { + super.reset(); + resetViewUI(); + } + + @Override protected void getTaskSize(DeviceProfile dp, Rect outRect) { LayoutUtils.calculateFallbackTaskSize(getContext(), dp, outRect); } @@ -115,6 +128,12 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity> { } @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 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity> { 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 it 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/inputconsumers/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java index 38b5a137c4..346969e592 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,7 +83,8 @@ public class AssistantTouchConsumer extends DelegateInputConsumer { 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; @@ -97,7 +99,8 @@ public class AssistantTouchConsumer extends DelegateInputConsumer { final Resources res = context.getResources(); mContext = context; mSysUiProxy = systemUiProxy; - mDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold); + 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 +120,6 @@ public class AssistantTouchConsumer extends DelegateInputConsumer { @Override public void onMotionEvent(MotionEvent ev) { // TODO add logging - mGestureDetector.onTouchEvent(ev); - switch (ev.getActionMasked()) { case ACTION_DOWN: { mActivePointerId = ev.getPointerId(0); @@ -213,6 +214,8 @@ public class AssistantTouchConsumer extends DelegateInputConsumer { break; } + mGestureDetector.onTouchEvent(ev); + if (mState != STATE_ACTIVE) { mDelegate.onMotionEvent(ev); } @@ -220,9 +223,9 @@ public class AssistantTouchConsumer extends DelegateInputConsumer { 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); @@ -271,7 +274,9 @@ public class AssistantTouchConsumer extends DelegateInputConsumer { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (isValidAssistantGestureAngle(velocityX, -velocityY) - && !mLaunchedAssistant && mState != STATE_DELEGATE_ACTIVE) { + && 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 db2af59aca..3d763ab52c 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 @@ -23,6 +23,7 @@ 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.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW; +import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync; import android.content.ComponentName; import android.content.Context; @@ -44,8 +45,6 @@ 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; @@ -209,9 +208,7 @@ public class DeviceLockedInputConsumer implements InputConsumer, .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 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 d05ca2a161..6ec1da0c48 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,443 @@ */ 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.RecentsView; +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 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"); + + public enum GestureEndTarget { + HOME(3, 100, 1), + RECENTS(1, 300, 0), + LAST_TASK(0, 150, 1), + NEW_TASK(0, 150, 1); + + private final float mEndProgress; + private final long mDurationMultiplier; + private final float mLauncherAlpha; + + GestureEndTarget(float endProgress, long durationMultiplier, float launcherAlpha) { + mEndProgress = endProgress; + mDurationMultiplier = durationMultiplier; + mLauncherAlpha = launcherAlpha; + } + } - private final ClipAnimationHelper mClipAnimationHelper; - private final TransformParams mTransformParams = new TransformParams(); - private final float mTransitionDragLength; - private final DeviceProfile mDP; + private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged); - private final RectF mSwipeTouchRegion; - private final boolean mDisableHorizontalSwipe; + private boolean mIsMotionPaused = false; + private GestureEndTarget mEndTarget; - private final PointF mDownPos = new PointF(); - private final PointF mLastPos = new PointF(); + private final boolean mInQuickSwitchMode; + private final boolean mContinuingLastGesture; + private final boolean mRunningOverHome; + private final boolean mSwipeUpOverHome; - private int mActivePointerId = -1; - // Slop used to determine when we say that the gesture has started. - private boolean mPassedPilferInputSlop; + private final RunningTaskInfo mRunningTaskInfo; + + private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f); + private RunningWindowAnim mFinishAnimation; + + public FallbackNoButtonInputConsumer(Context context, + OverviewComponentObserver overviewComponentObserver, + RunningTaskInfo runningTaskInfo, RecentsModel recentsModel, + InputConsumerController inputConsumer, + boolean isLikelyToStartNewTask, boolean continuingLastGesture) { + super(context, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id); + mLauncherAlpha.value = 1; + + mRunningTaskInfo = runningTaskInfo; + mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture; + mContinuingLastGesture = continuingLastGesture; + mRunningOverHome = ActivityManagerWrapper.isHomeTask(runningTaskInfo); + mSwipeUpOverHome = mRunningOverHome && !mInQuickSwitchMode; + + if (mSwipeUpOverHome) { + mClipAnimationHelper.setBaseAlphaCallback((t, a) -> 1 - mLauncherAlpha.value); + } else { + mClipAnimationHelper.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value); + } - private VelocityTracker mVelocityTracker; + initStateCallbacks(); + } - // Distance after which we start dragging the window. - private final float mTouchSlop; + private void initStateCallbacks() { + mStateCallback = new MultiStateCallback(STATE_NAMES); - // 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; + mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, + this::onHandlerInvalidated); + mStateCallback.addCallback(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED, + this::onHandlerInvalidatedWithRecents); - private int mState = STATE_NOT_FINISHED; + mStateCallback.addCallback(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED, + this::finishAnimationTargetSetAnimationComplete); - 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; - - mSwipeSharedState = swipeSharedState; - mSwipeTouchRegion = swipeTouchRegion; - mDisableHorizontalSwipe = disableHorizontalSwipe; - - 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); + 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); + } } - @Override - public int getType() { - return TYPE_FALLBACK_NO_BUTTON; + private void onLauncherAlphaChanged() { + if (mRecentsAnimationWrapper.targetSet != null && mEndTarget == null) { + applyTransformUnchecked(); + } } @Override - public void onMotionEvent(MotionEvent ev) { - if (mVelocityTracker == null) { - return; + 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; + } - mVelocityTracker.addMovement(ev); - if (ev.getActionMasked() == ACTION_POINTER_UP) { - mVelocityTracker.clear(); - } + @Override + protected boolean moveWindowWithRecentsScroll() { + return mInQuickSwitchMode; + } - 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; - } + @Override + public void initWhenReady() { + if (mInQuickSwitchMode) { + // Only init if we are in quickswitch mode + super.initWhenReady(); } } - private void startTouchTrackingForWindowAnimation(float displacement) { - mStartDisplacement = Math.min(displacement, -mTouchSlop); + @Override + public void updateDisplacement(float displacement) { + if (!mInQuickSwitchMode) { + super.updateDisplacement(displacement); + } + } - RecentsAnimationListenerSet listenerSet = - mSwipeSharedState.newRecentsAnimationListenerSet(); - listenerSet.addListener(this); - Intent homeIntent = mOverviewComponentObserver.getHomeIntent(); - BackgroundExecutor.get().submit( - () -> ActivityManagerWrapper.getInstance().startRecentsActivity( - homeIntent, null, listenerSet, null, null)); + @Override + protected InputConsumer createNewInputProxyHandler() { + // Just consume all input on the active task + return InputConsumer.NO_OP; + } - ActivityManagerWrapper.getInstance().closeSystemWindows( - CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); - mInputMonitor.pilferPointers(); + @Override + public void onMotionPauseChanged(boolean isPaused) { + if (!mInQuickSwitchMode) { + mIsMotionPaused = isPaused; + mLauncherAlpha.animateToValue(mLauncherAlpha.value, isPaused ? 0 : 1) + .setDuration(150).start(); + performHapticFeedback(); + } } - private void updateDisplacement(float displacement) { - mProgress = displacement / mTransitionDragLength; - mTransformParams.setProgress(mProgress); + @Override + public Intent getLaunchIntent() { + if (mInQuickSwitchMode || mSwipeUpOverHome) { + return mOverviewComponentObserver.getOverviewIntent(); + } else { + return mOverviewComponentObserver.getHomeIntent(); + } + } - if (mSwipeAnimationTargetSet != null) { - mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams); + @Override + public void updateFinalShift() { + mTransformParams.setProgress(mCurrentShift.value); + mRecentsAnimationWrapper.setWindowThresholdCrossed(!mInQuickSwitchMode + && (mCurrentShift.value > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD)); + if (mRecentsAnimationWrapper.targetSet != null) { + applyTransformUnchecked(); } } - private void forceCancelGesture(MotionEvent ev) { - int action = ev.getAction(); - ev.setAction(ACTION_CANCEL); - finishTouchTracking(ev); - ev.setAction(action); + @Override + public void onGestureCancelled() { + updateDisplacement(0); + mEndTarget = LAST_TASK; + setStateOnUiThread(STATE_GESTURE_CANCELLED); } - /** - * 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; + @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 { - 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; 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); + } - if (goingHome) { - mState = STATE_FINISHED_TO_HOME; - } else { - mState = STATE_FINISHED_TO_APP; + @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 (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); } + } - if (mSwipeAnimationTargetSet != null) { - finishAnimationTargetSet(); + 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; + } + } + + 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; + 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)); - 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.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 */); - - overviewStackBounds - .inset(-overviewStackBounds.width() / 5, -overviewStackBounds.height() / 5); - mClipAnimationHelper.updateTargetRect(overviewStackBounds); - mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams); + applyTransformUnchecked(); - 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 f5cf654b15..a1e5d47a53 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 @@ public interface InputConsumer { 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 @@ public interface InputConsumer { "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 4c137d3bf9..86766d99fb 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 @@ -28,13 +28,13 @@ 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.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; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.content.ContextWrapper; -import android.content.Intent; import android.graphics.PointF; import android.graphics.RectF; import android.os.Build; @@ -48,21 +48,16 @@ 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.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; @@ -83,16 +78,14 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC 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 +93,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC 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 +107,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC 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; @@ -128,20 +121,18 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC }; 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, + BaseSwipeUpHandler.Factory handlerFactory) { super(base); 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 +141,9 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC 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 +152,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC float slop = QUICKSTEP_TOUCH_SLOP_RATIO * mTouchSlop; mSquaredTouchSlop = slop * slop; - mPassedPilferInputSlop = mPaddedWindowMoveSlop = continuingPreviousGesture; + mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture; mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe; } @@ -186,7 +175,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC } // Proxy events to recents view - if (mPaddedWindowMoveSlop && mInteractionHandler != null + if (mPassedWindowMoveSlop && mInteractionHandler != null && !mRecentsViewDispatcher.hasConsumer()) { mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher( mNavBarPosition.getRotationMode())); @@ -213,7 +202,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC // 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 +240,21 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC 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 +270,11 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC 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 +283,12 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC } 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 +320,14 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC 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 +341,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC RecentsAnimationListenerSet newListenerSet = mSwipeSharedState.newRecentsAnimationListenerSet(); newListenerSet.addListener(handler); - BackgroundExecutor.get().submit( - () -> ActivityManagerWrapper.getInstance().startRecentsActivity( - mHomeIntent, null, newListenerSet, null, null)); + startRecentsActivityAsync(handler.getLaunchIntent(), newListenerSet); } } @@ -366,7 +353,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC 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 +396,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC // 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 b021df877e..581ab6d3ea 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.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TI 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 androidx.annotation.Nullable; 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 425b8b6b0b..05cbb789d9 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 @@ -30,7 +30,13 @@ 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.launcher3.logging.StatsLogUtils; +import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; +import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; +import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.quickstep.OverviewCallbacks; import com.android.quickstep.util.NavBarPosition; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -131,12 +137,14 @@ public class OverviewWithoutFocusInputConsumer implements InputConsumer { ? -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) { @@ -144,6 +152,13 @@ public class OverviewWithoutFocusInputConsumer implements InputConsumer { ActivityManagerWrapper.getInstance() .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); 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/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java index 6dc672ecfa..90989feb4d 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 @@ -99,8 +99,8 @@ public class ClipAnimationHelper { // 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 +119,12 @@ public class ClipAnimationHelper { } 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 +191,12 @@ public class ClipAnimationHelper { 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); @@ -214,9 +218,12 @@ public class ClipAnimationHelper { // 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 +254,14 @@ public class ClipAnimationHelper { } } - 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 +367,10 @@ public class ClipAnimationHelper { return mCurrentCornerRadius; } + public interface TargetAlphaProvider { + float getAlpha(RemoteAnimationTargetCompat target, float expectedAlpha); + } + public static class TransformParams { float progress; public float offsetX; 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 0000000000..abfe3adbff --- /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 83601e6175..14083dd958 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 @@ -23,6 +23,7 @@ import android.util.ArraySet; 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; @@ -39,7 +40,7 @@ import androidx.annotation.UiThread; 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; @@ -90,14 +91,14 @@ public class RecentsAnimationListenerSet implements RecentsAnimationListener { } @Override - public final void onAnimationCanceled(boolean deferredWithScreenshot) { + public final void onAnimationCanceled(ThumbnailData thumbnailData) { Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> { for (SwipeAnimationListener listener : getListeners()) { listener.onRecentsAnimationCanceled(); } }); // TODO: handle the transition better instead of simply using a transition delay. - if (deferredWithScreenshot) { + if (thumbnailData != null) { MAIN_THREAD_EXECUTOR.getHandler().postDelayed(() -> mController.cleanupScreenshot(), TRANSITION_DELAY); } @@ -109,6 +110,6 @@ public class RecentsAnimationListenerSet implements RecentsAnimationListener { 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 77dc6f32e7..c6eafe6a64 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 class RectFSpringAnim { } } + 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/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index 07e96869ed..1aa5365fd2 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,13 +15,21 @@ */ 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.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.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; @@ -29,18 +37,17 @@ import com.android.launcher3.LauncherState; 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.folder.FolderIcon; +import com.android.launcher3.graphics.OverviewScrim; +import com.android.launcher3.views.IconLabelDotView; 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,7 +65,9 @@ public class StaggeredWorkspaceAnim { private final float mVelocity; private final float mSpringTransY; - private final View mViewToIgnore; + + // The original view of the {@link FloatingIconView}. + private final View mOriginalView; private final List<Animator> mAnimators = new ArrayList<>(); @@ -68,9 +77,7 @@ public class StaggeredWorkspaceAnim { public StaggeredWorkspaceAnim(Launcher launcher, @Nullable View floatingViewOriginalView, float velocity) { mVelocity = velocity; - // We ignore this view since it's visibility and position is controlled by - // the FloatingIconView. - mViewToIgnore = floatingViewOriginalView; + mOriginalView = floatingViewOriginalView; // Scale the translationY based on the initial velocity to better sync the workspace items // with the floating view. @@ -79,9 +86,24 @@ public class StaggeredWorkspaceAnim { .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 +116,48 @@ public class StaggeredWorkspaceAnim { } // 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); + 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); + } } /** @@ -134,10 +181,6 @@ public class StaggeredWorkspaceAnim { * @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. @@ -149,21 +192,48 @@ public class StaggeredWorkspaceAnim { springTransY.setStartDelay(startDelay); mAnimators.add(springTransY); + ObjectAnimator alpha = getAlphaAnimator(v, startDelay); + if (v == mOriginalView) { + // For IconLabelDotViews, we just want the label to fade in. + // Icon, badge, and dots will animate in separately (controlled via FloatingIconView) + if (v instanceof IconLabelDotView) { + alpha.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + IconLabelDotView view = (IconLabelDotView) v; + view.setIconVisible(false); + view.setForceHideDot(true); + } + }); + } else { + return; + } + } + v.setAlpha(0); + mAnimators.add(alpha); + } + + private ObjectAnimator getAlphaAnimator(View v, long startDelay) { ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f, 1f); alpha.setInterpolator(LINEAR); alpha.setDuration(ALPHA_DURATION_MS); alpha.setStartDelay(startDelay); - mAnimators.add(alpha); + return 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/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index a98df0fa1f..ef54d3f892 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; @@ -37,6 +36,7 @@ import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON; import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; +import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR; import android.animation.Animator; import android.animation.AnimatorSet; @@ -72,6 +72,7 @@ import android.view.MotionEvent; 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.graphics.RotationMode; 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.quickstep.util.ClipAnimationHelper; 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; @@ -226,7 +227,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl return; } - BackgroundExecutor.get().submit(() -> { + BACKGROUND_EXECUTOR.execute(() -> { TaskView taskView = getTaskView(taskId); if (taskView == null) { return; @@ -269,7 +270,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl private int mTaskListChangeId = -1; // Only valid until the launcher state changes to NORMAL - private int mRunningTaskId = -1; + protected int mRunningTaskId = -1; private boolean mRunningTaskTileHidden; private Task mTmpRunningTask; @@ -289,7 +290,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl @ViewDebug.ExportedProperty(category = "launcher") private 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; @@ -527,7 +528,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl return true; } - private void applyLoadPlan(ArrayList<Task> tasks) { + protected void applyLoadPlan(ArrayList<Task> tasks) { if (mPendingAnimation != null) { mPendingAnimation.addEndListener((onEndListener) -> applyLoadPlan(tasks)); return; @@ -570,9 +571,13 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl 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)); + } } if (mIgnoreResetTaskId != -1 && getTaskView(mIgnoreResetTaskId) != ignoreRestTaskView) { @@ -599,6 +604,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl TaskView taskView = (TaskView) getChildAt(i); if (mIgnoreResetTaskId != taskView.getTask().key.id) { taskView.resetVisualProperties(); + taskView.setStableAlpha(mContentAlpha); } } if (mRunningTaskTileHidden) { @@ -783,6 +789,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl unloadVisibleTaskData(); setCurrentPage(0); mDwbToastShown = false; + mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0); } public @Nullable TaskView getRunningTaskView() { @@ -821,7 +828,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl */ 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); @@ -848,12 +855,14 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl * is called. Also scrolls the view to this task. */ public void showCurrentTask(int runningTaskId) { - if (getChildCount() == 0) { + if (getTaskView(runningTaskId) == null) { + boolean wasEmpty = getChildCount() == 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, 0); + 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(), @@ -935,6 +944,10 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl } } + public boolean isTaskIconScaledDown(TaskView taskView) { + return mRunningTaskIconScaledDown && getRunningTaskView() == taskView; + } + private void applyRunningTaskIconScale() { TaskView firstTask = getRunningTaskView(); if (firstTask != null) { @@ -1046,9 +1059,10 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl 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); } } } @@ -1687,6 +1701,9 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl * @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(); @@ -1733,4 +1750,14 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl 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()); + } } 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 d55a520443..7f1e8980b9 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 @@ -410,4 +410,11 @@ public class TaskThumbnailView extends View { 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 b26fdce92f..bfb961320d 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.AnimatorListenerAdapter; 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; @@ -54,7 +53,6 @@ import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.logging.UserEventDispatcher; -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; @@ -192,9 +190,6 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { 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 +286,6 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { 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 +300,6 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { 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 +816,11 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { / (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-fr/strings.xml b/quickstep/res/values-fr/strings.xml index 5394f495e9..01dcff202d 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 0467af4b2d..387d509587 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 1ca558a240..cccece749c 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/dimens.xml b/quickstep/res/values/dimens.xml index b0968f94c0..78424ca516 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 78f6ffa88c..a8e29569b1 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.Animator; 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 @@ public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCo /** * 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 @@ public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCo } @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 @@ public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCo // 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 c5c5323ec5..b9ce1ceee5 100644 --- a/quickstep/src/com/android/launcher3/LauncherInitListener.java +++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java @@ -85,7 +85,7 @@ public class LauncherInitListener extends InternalStateHandler implements Activi 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 b60a017dd8..991408c649 100644 --- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java +++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java @@ -219,7 +219,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans anim.addListener(mForceInvisibleListener); } - result.setAnimation(anim); + result.setAnimation(anim, mLauncher); } }; @@ -822,7 +822,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans } 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 f0204b9ba4..174e49b8d2 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java @@ -19,16 +19,20 @@ package com.android.launcher3.uioverrides; 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.AnimationConfig; 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 @@ public abstract class BaseRecentsViewStateController<T extends View> 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 @@ public abstract class BaseRecentsViewStateController<T extends View> 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 0000000000..853a1c6acb --- /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 97cd38a117..c02df9386c 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java +++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java @@ -32,9 +32,11 @@ import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN; 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; @@ -244,4 +246,9 @@ public class UiFactory extends RecentsUiFactory { } 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; + } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java index 0605953dce..bb72315d3b 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java @@ -46,7 +46,7 @@ public class LandscapeEdgeSwipeController extends AbstractStateChangeTouchContro } @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 6030cea938..b81edfa4ca 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java @@ -51,7 +51,6 @@ import com.android.quickstep.OverviewInteractionState; 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 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr /** * 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. @@ -148,8 +147,8 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr } @Override - protected int getLogContainerTypeForNormalState() { - return ContainerType.HOTSEAT; + protected int getLogContainerTypeForNormalState(MotionEvent ev) { + return isTouchOverHotseat(mLauncher, ev) ? ContainerType.HOTSEAT : ContainerType.WORKSPACE; } private AnimatorSetBuilder getNormalToOverviewAnimation() { @@ -296,9 +295,13 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr * @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 fee18204ea..11a804356d 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 @@ package com.android.launcher3.uioverrides.touchcontrollers; 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 @@ import java.io.PrintWriter; /** * 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 @@ public class StatusBarTouchController implements TouchController { 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 @@ public class StatusBarTouchController implements TouchController { 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 @@ public class StatusBarTouchController implements TouchController { @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 @@ public class StatusBarTouchController implements TouchController { 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 @@ public class StatusBarTouchController implements TouchController { 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 cd2c9cb1bf..5c9c7d4cab 100644 --- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java +++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java @@ -152,5 +152,15 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> { 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/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java index 0738affa93..88a4eb6d11 100644 --- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java +++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java @@ -29,11 +29,15 @@ import android.content.ComponentName; 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 +57,41 @@ public final class OverviewComponentObserver { } }; 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)); @@ -92,17 +115,22 @@ public final class OverviewComponentObserver { 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 +139,12 @@ public final class OverviewComponentObserver { } } 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 +164,6 @@ public final class OverviewComponentObserver { 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 +179,32 @@ public final class OverviewComponentObserver { } /** + * @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 +217,7 @@ public final class OverviewComponentObserver { * 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/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java index 7bfa9a0f99..befeee0db9 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 android.util.Log; 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 89513634fe..0000000000 --- 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 f27ba85388..e41dba94cc 100644 --- a/quickstep/src/com/android/quickstep/RecentTasksList.java +++ b/quickstep/src/com/android/quickstep/RecentTasksList.java @@ -16,6 +16,8 @@ package com.android.quickstep; +import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR; + import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.Context; @@ -25,10 +27,7 @@ import android.util.SparseBooleanArray; import com.android.launcher3.MainThreadExecutor; 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; @@ -43,7 +42,6 @@ public class RecentTasksList extends TaskStackChangeListener { private final KeyguardManagerCompat mKeyguardManager; private final MainThreadExecutor mMainThreadExecutor; - private final BackgroundExecutor mBgThreadExecutor; // The list change id, increments as the task list changes in the system private int mChangeId; @@ -56,7 +54,6 @@ public class RecentTasksList extends TaskStackChangeListener { public RecentTasksList(Context context) { mMainThreadExecutor = new MainThreadExecutor(); - mBgThreadExecutor = BackgroundExecutor.get(); mKeyguardManager = new KeyguardManagerCompat(context); mChangeId = 1; ActivityManagerWrapper.getInstance().registerTaskStackListener(this); @@ -67,7 +64,7 @@ public class RecentTasksList extends TaskStackChangeListener { */ public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) { // Kick off task loading in the background - mBgThreadExecutor.submit(() -> { + BACKGROUND_EXECUTOR.execute(() -> { ArrayList<Task> tasks = loadTasksInBackground(numTasks, true /* loadKeysOnly */); mMainThreadExecutor.execute(() -> callback.accept(tasks)); }); @@ -87,13 +84,14 @@ public class RecentTasksList extends TaskStackChangeListener { : () -> 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.getHandler().post(resultCallback); return requestLoadId; } // Kick off task loading in the background - mBgThreadExecutor.submit(() -> { + BACKGROUND_EXECUTOR.execute(() -> { ArrayList<Task> tasks = loadTasksInBackground(Integer.MAX_VALUE, loadKeysOnly); mMainThreadExecutor.execute(() -> { @@ -121,12 +119,7 @@ public class RecentTasksList extends TaskStackChangeListener { @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 @@ -166,15 +159,11 @@ public class RecentTasksList extends TaskStackChangeListener { 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 0822e6199b..f9d2f11cba 100644 --- a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java +++ b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java @@ -68,7 +68,7 @@ public class RecentsActivityTracker<T extends BaseRecentsActivity> implements Ac 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); } diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java index 9f12484589..dfab43459d 100644 --- a/quickstep/src/com/android/quickstep/RecentsModel.java +++ b/quickstep/src/com/android/quickstep/RecentsModel.java @@ -16,15 +16,12 @@ package com.android.quickstep; 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.Process; import android.os.RemoteException; @@ -35,7 +32,6 @@ 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.TaskStackChangeListener; import java.util.ArrayList; @@ -52,7 +48,7 @@ public class RecentsModel extends TaskStackChangeListener { // 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; diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java index 2f411efc7b..bf3cd8afe2 100644 --- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java +++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java @@ -16,18 +16,19 @@ 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.view.View; import com.android.launcher3.ItemInfo; @@ -38,8 +39,6 @@ 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. * @@ -74,6 +73,27 @@ public class StatsLogCompatManager extends StatsLogManager { 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) { StatsLogUtils.LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(v); if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) { @@ -88,6 +108,13 @@ public class StatsLogCompatManager extends StatsLogManager { return true; } + public static boolean fillInLauncherExtensionWithPageId(LauncherExtension ext, int pageId) { + Target target = new Target(); + target.pageIndex = pageId; + copy(target, ext.srcTarget[0]); + return true; + } + private static void copy(Target src, LauncherTarget dst) { // fill in } diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java index a7e6d74f02..4503a43542 100644 --- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java +++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java @@ -17,6 +17,7 @@ package com.android.quickstep.util; 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 @@ public interface RemoteAnimationProvider { 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 63c8023f6d..dc6b56eecf 100644 --- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java +++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java @@ -74,6 +74,9 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis 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 +98,7 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis 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 +153,18 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis 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); - + float dragHandleTop = + Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(context, dp)); + mDragHandleProgress = 1 - (dragHandleTop / mShiftRange); } mTopOffset = dp.getInsets().top - mShelfOffset; mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset; @@ -199,8 +205,6 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis 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 +216,10 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis (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 046d871163..02e8ebcaba 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 d9fcf4d97a..d0956d1f6d 100644 --- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java +++ b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java @@ -88,7 +88,7 @@ public class AppPredictionsUITests extends AbstractQuickStepTest { */ @Test public void testPredictionExistsInAllApps() { - mActivityMonitor.startLauncher(); + mDevice.pressHome(); mLauncher.pressHome().switchToAllApps(); // Dispatch an update @@ -103,7 +103,7 @@ public class AppPredictionsUITests extends AbstractQuickStepTest { */ @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 @@ public class AppPredictionsUITests extends AbstractQuickStepTest { @Test public void testPredictionsDisabled() { - mActivityMonitor.startLauncher(); + mDevice.pressHome(); sendPredictionUpdate(); mLauncher.pressHome().switchToAllApps(); diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java index 0c5a6f5b6e..a7c33a9549 100644 --- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java +++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java @@ -51,7 +51,7 @@ public class DigitalWellBeingToastTest extends AbstractQuickStepTest { 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 @@ public class DigitalWellBeingToastTest extends AbstractQuickStepTest { 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/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java index 3b35c86af8..e29552713d 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_ 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 @@ public class NavigationModeSwitchRule implements TestRule { 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 @@ public class NavigationModeSwitchRule implements TestRule { 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, @@ -154,7 +184,7 @@ public class NavigationModeSwitchRule implements TestRule { latch.await(10, 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 @@ public class NavigationModeSwitchRule implements TestRule { 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 @@ public class NavigationModeSwitchRule implements TestRule { 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/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java index 2111e2ca27..c5b560c3fc 100644 --- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java +++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java @@ -25,6 +25,7 @@ import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; import com.android.launcher3.Launcher; +import com.android.launcher3.tapl.LauncherInstrumentation; import com.android.launcher3.util.RaceConditionReproducer; import com.android.quickstep.NavigationModeSwitchRule.Mode; import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch; @@ -79,6 +80,8 @@ public class StartLauncherViaGestureTests extends AbstractQuickStepTest { @Test @NavigationModeSwitch public void testStressPressHome() { + if (LauncherInstrumentation.isAvd()) return; // b/136278866 + for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) { // Destroy Launcher activity. closeLauncherActivity(); @@ -91,6 +94,8 @@ public class StartLauncherViaGestureTests extends AbstractQuickStepTest { @Test @NavigationModeSwitch public void testStressSwipeToOverview() { + if (LauncherInstrumentation.isAvd()) return; // b/136278866 + for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) { // Destroy Launcher activity. closeLauncherActivity(); diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java index 9e3bf2f56e..885fdbf423 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java @@ -17,7 +17,6 @@ package com.android.quickstep; import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -208,7 +207,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { @Test @NavigationModeSwitch -// @PortraitLandscape + @PortraitLandscape public void testBackground() throws Exception { startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); final Background background = mLauncher.getBackground(); diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index c0859d3b4d..50f92daf28 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -88,7 +88,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-hi/strings.xml b/res/values-hi/strings.xml index b796140e5e..3b0432ee4a 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -88,7 +88,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-in/strings.xml b/res/values-in/strings.xml index 3054214b5e..211f735b49 100644 --- a/res/values-in/strings.xml +++ b/res/values-in/strings.xml @@ -30,10 +30,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 +41,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 +73,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-mr/strings.xml b/res/values-mr/strings.xml index 19c06976b0..49e38982cd 100644 --- a/res/values-mr/strings.xml +++ b/res/values-mr/strings.xml @@ -35,18 +35,18 @@ <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_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> @@ -136,7 +136,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-ru/strings.xml b/res/values-ru/strings.xml index 34e267da9b..23b00d0936 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -90,7 +90,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-ta/strings.xml b/res/values-ta/strings.xml index 672665e430..c926bc185b 100644 --- a/res/values-ta/strings.xml +++ b/res/values-ta/strings.xml @@ -89,7 +89,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/config.xml b/res/values/config.xml index 638a411be6..038718473b 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> diff --git a/res/values/styles.xml b/res/values/styles.xml index 881f65d276..339aef5b60 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> diff --git a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java index 92bcc64348..a3d121676f 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 @@ public final class FlagOverrideRule implements TestRule { }; } - 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 @@ public final class FlagOverrideRule implements TestRule { 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 @@ public final class FlagOverrideRule implements TestRule { * 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/PackageInstallStateChangedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java index e9324f9ce2..42a4f5cc99 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 @@ public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestC 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/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java index 733f29540f..8b49c06389 100644 --- a/src/com/android/launcher3/AllAppsList.java +++ b/src/com/android/launcher3/AllAppsList.java @@ -89,7 +89,7 @@ public class AllAppsList { 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); diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java index d9491419fd..e3ef5d64e7 100644 --- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java +++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java @@ -11,6 +11,7 @@ import android.database.Cursor; 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 com.android.launcher3.util.ContentWriter; 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 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver { 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 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver { 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/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java index f69b172480..f0b3afd14a 100644 --- a/src/com/android/launcher3/BaseDraggingActivity.java +++ b/src/com/android/launcher3/BaseDraggingActivity.java @@ -135,10 +135,6 @@ public abstract class BaseDraggingActivity extends BaseActivity 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)) { Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); return false; @@ -162,10 +158,6 @@ public abstract class BaseDraggingActivity extends BaseActivity 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 +170,7 @@ public abstract class BaseDraggingActivity extends BaseActivity 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); } diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java index c84be4dacd..864fa6e8a6 100644 --- a/src/com/android/launcher3/BaseRecyclerView.java +++ b/src/com/android/launcher3/BaseRecyclerView.java @@ -22,6 +22,7 @@ import android.view.MotionEvent; 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 @@ public abstract class BaseRecyclerView extends RecyclerView { * <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 1619e3645f..b1132494a2 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -32,7 +32,6 @@ import android.graphics.drawable.ColorDrawable; 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.DotRenderer; 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 @@ import java.text.NumberFormat; * 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; @@ -319,9 +319,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, @Override public boolean onTouchEvent(MotionEvent event) { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.NO_START_TAG, "BubbleTextView.onTouchEvent " + event); - } // Call the superclass onTouchEvent first, because sometimes it changes the state to // isPressed() on an ACTION_UP boolean result = super.onTouchEvent(event); @@ -416,7 +413,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } } - public void forceHideDot(boolean forceHideDot) { + @Override + public void setForceHideDot(boolean forceHideDot) { if (mForceHideDot == forceHideDot) { return; } @@ -605,6 +603,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } } + @Override public void setIconVisible(boolean visible) { mIsIconVisible = visible; Drawable icon = visible ? mIcon : new ColorDrawable(Color.TRANSPARENT); diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java index 7ab88a0083..a90025e97f 100644 --- a/src/com/android/launcher3/FastBitmapDrawable.java +++ b/src/com/android/launcher3/FastBitmapDrawable.java @@ -142,8 +142,11 @@ public class FastBitmapDrawable extends Drawable { @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 c967a96e48..6c5bc40b3e 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 @@ public class FirstFrameAnimatorHelper implements OnDrawListener, OnAttachStateCh // 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/IconProvider.java b/src/com/android/launcher3/IconProvider.java index e1ef9548cd..0f006f7c19 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 e9b932aaf5..89ec2a5647 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -19,6 +19,7 @@ package com.android.launcher3; 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; @@ -29,7 +30,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; @@ -39,6 +39,9 @@ import android.util.Base64; 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; @@ -141,7 +144,8 @@ public class InstallShortcutReceiver extends BroadcastReceiver { String pkg = getIntentPackage(info.launchIntent); if (!TextUtils.isEmpty(pkg) - && !launcherApps.isPackageEnabledForProfile(pkg, info.user)) { + && !launcherApps.isPackageEnabledForProfile(pkg, info.user) + && !info.isActivity) { if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: " + info.launchIntent); continue; @@ -249,10 +253,6 @@ public class InstallShortcutReceiver extends BroadcastReceiver { 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 +261,9 @@ public class InstallShortcutReceiver extends BroadcastReceiver { 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) { @@ -326,11 +327,11 @@ public class InstallShortcutReceiver extends BroadcastReceiver { 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 +341,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { * 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 +351,22 @@ public class InstallShortcutReceiver extends BroadcastReceiver { 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 +377,24 @@ public class InstallShortcutReceiver extends BroadcastReceiver { /** * 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 +411,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { */ public PendingInstallShortcutInfo( AppWidgetProviderInfo info, int widgetId, Context context) { - activityInfo = null; + isActivity = false; shortcutInfo = null; providerInfo = info; @@ -405,17 +426,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { 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 +460,24 @@ public class InstallShortcutReceiver extends BroadcastReceiver { // 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(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 +492,18 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } 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, + 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 +515,16 @@ public class InstallShortcutReceiver extends BroadcastReceiver { 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, LauncherAppState.getInstance(mContext)); + return Pair.create(itemInfo, null); } } public boolean isLauncherActivity() { - return activityInfo != null; + return isActivity; } } @@ -534,7 +539,9 @@ public class InstallShortcutReceiver extends BroadcastReceiver { 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 +585,11 @@ public class InstallShortcutReceiver extends BroadcastReceiver { 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); } @@ -626,6 +637,11 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, 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); diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java index 134e116063..3f723d17f1 100644 --- a/src/com/android/launcher3/ItemInfo.java +++ b/src/com/android/launcher3/ItemInfo.java @@ -22,6 +22,8 @@ import android.content.Intent; import android.os.Process; import android.os.UserHandle; +import androidx.annotation.Nullable; + import com.android.launcher3.util.ContentWriter; /** @@ -134,6 +136,7 @@ public class ItemInfo { return null; } + @Nullable public ComponentName getTargetComponent() { Intent intent = getIntent(); if (intent != null) { diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index bc3aa7ef40..257f0dfcf3 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -127,6 +127,7 @@ import com.android.launcher3.util.PackageManagerHelper; 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; @@ -156,6 +157,7 @@ import java.util.List; import java.util.function.Predicate; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; /** * Default launcher application. @@ -208,9 +210,9 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, 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; @@ -873,9 +875,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, if (mLauncherCallbacks != null) { mLauncherCallbacks.onStop(); } - - getUserEventDispatcher().logActionCommand(Action.Command.STOP, - mStateManager.getState().containerType, -1); + logStopAndResume(Action.Command.STOP); mAppWidgetHost.setListenIfResumed(false); @@ -890,9 +890,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, @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 +901,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, 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,6 +928,17 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } } + 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); + } + + } + protected void onStateSet(LauncherState state) { getAppWidgetHost().setResumed(state == LauncherState.NORMAL); if (mDeferredResumePending) { @@ -1809,11 +1816,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, 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 +1906,10 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, 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); } } @@ -2257,9 +2263,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, @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); @@ -2500,7 +2504,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, 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)); diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index d07638a5c1..b4a2216c57 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -19,14 +19,12 @@ package com.android.launcher3; 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; @@ -46,7 +44,7 @@ public class LauncherAppState { // 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 +94,7 @@ public class LauncherAppState { 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(); diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index d79f5d5a94..a0414894b1 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -20,6 +20,7 @@ import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD; import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ShortcutInfo; @@ -51,6 +52,7 @@ 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; @@ -211,6 +213,30 @@ public class LauncherModel extends BroadcastReceiver 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); diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java index dcfd272b47..6e2626b615 100644 --- a/src/com/android/launcher3/LauncherState.java +++ b/src/com/android/launcher3/LauncherState.java @@ -243,6 +243,10 @@ public class LauncherState { 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 2c8c208456..63914b0eb5 100644 --- a/src/com/android/launcher3/LauncherStateManager.java +++ b/src/com/android/launcher3/LauncherStateManager.java @@ -227,6 +227,11 @@ public class LauncherStateManager { private void goToState(LauncherState state, boolean animated, long delay, final Runnable onCompleteRunnable) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.ALL_APPS_UPON_RECENTS, "goToState: " + + state.getClass().getSimpleName() + + " @ " + Log.getStackTraceString(new Throwable())); + } animated &= Utilities.areAnimationsEnabled(mLauncher); if (mLauncher.isInState(state)) { if (mConfig.mCurrentAnimation == null) { @@ -403,14 +408,15 @@ public class LauncherStateManager { } 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; + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.STABLE_STATE_MISMATCH, "onStateTransitionStart: " + + state.getClass().getSimpleName() + + " @ " + Log.getStackTraceString(new Throwable())); + } mState.onStateEnabled(mLauncher); mLauncher.onStateSet(mState); @@ -429,12 +435,12 @@ public class LauncherStateManager { // Only change the stable states after the transitions have finished if (state != mCurrentStableState) { mLastStableState = state.getHistoryForState(mCurrentStableState); + mCurrentStableState = state; if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, - "mCurrentStableState = " + state.getClass().getSimpleName() + " @ " + - android.util.Log.getStackTraceString(new Throwable())); + Log.d(TestProtocol.ALL_APPS_UPON_RECENTS, "onStateTransitionEnd: " + + state.getClass().getSimpleName() + + " @ " + Log.getStackTraceString(new Throwable())); } - mCurrentStableState = state; } state.onStateTransitionEnd(mLauncher); @@ -580,10 +586,6 @@ public class LauncherStateManager { 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/PagedView.java b/src/com/android/launcher3/PagedView.java index 2eeb132bbf..79d6ca7934 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.view.animation.Interpolator; 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 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou */ protected void onPageEndTransition() { mWasInOverscroll = false; + AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext()); } protected int getUnboundedScrollX() { @@ -847,7 +848,7 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou */ // 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 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou 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 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou 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 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou if (mFreeScroll) { setCurrentPage(getNextPage()); } else if (wasFreeScroll) { - snapToPage(getNextPage()); + if (getScrollForPage(getNextPage()) != getScrollX()) { + snapToPage(getNextPage()); + } } } @@ -1095,7 +1101,7 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou @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 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou 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. @@ -1561,12 +1569,20 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou final boolean pagesFlipped = isPageOrderFlipped(); info.setScrollable(getPageCount() > 1); if (getCurrentPage() < getPageCount() - 1) { - info.addAction(pagesFlipped ? AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD - : AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + 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); + 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. @@ -1606,8 +1622,21 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou if (pagesFlipped ? scrollRight() : scrollLeft()) { return true; } + } break; + case android.R.id.accessibilityActionPageRight: { + if (!mIsRtl) { + return scrollRight(); + } else { + return scrollLeft(); + } + } + case android.R.id.accessibilityActionPageLeft: { + if (!mIsRtl) { + return scrollLeft(); + } else { + return scrollRight(); + } } - break; } return false; } diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java index b0da6b9cd9..b4078ee0ea 100644 --- a/src/com/android/launcher3/SessionCommitReceiver.java +++ b/src/com/android/launcher3/SessionCommitReceiver.java @@ -18,28 +18,33 @@ package com.android.launcher3; import android.annotation.TargetApi; 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.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; 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.compat.PackageInstallerCompat; import java.util.List; +import static com.android.launcher3.compat.PackageInstallerCompat.getUserHandle; + /** * BroadcastReceiver to handle session commit intent. */ @@ -66,15 +71,29 @@ public class SessionCommitReceiver extends BroadcastReceiver { SessionInfo info = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION); UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER); + PackageInstallerCompat packageInstallerCompat = PackageInstallerCompat.getInstance(context); - if (TextUtils.isEmpty(info.getAppPackageName()) || - info.getInstallReason() != PackageManager.INSTALL_REASON_USER) { + 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 +101,18 @@ public class SessionCommitReceiver extends BroadcastReceiver { // 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) { diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 65aa3a775d..ba122f9441 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -16,9 +16,12 @@ 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; @@ -92,8 +95,6 @@ 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 +110,9 @@ public final class Utilities { 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 = @@ -128,16 +132,6 @@ public final class Utilities { 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. */ @@ -738,7 +732,7 @@ public final class Utilities { int[] array = new int[tokenizer.countTokens()]; int count = 0; while (tokenizer.hasMoreTokens()) { - array[count] = Integer.parseInt(tokenizer.nextToken()); + array[count] = Integer.parseInt(tokenizer.nextToken().trim()); count++; } return array; diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 9f846bbc4d..6612662ea5 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -81,6 +81,7 @@ import com.android.launcher3.folder.PreviewBackground; 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; @@ -370,10 +371,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator> @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 +422,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator> // 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); } @@ -1056,13 +1052,18 @@ public class Workspace extends PagedView<WorkspacePageIndicator> 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 @@ -1746,6 +1747,9 @@ public class Workspace extends PagedView<WorkspacePageIndicator> 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 +2427,9 @@ public class Workspace extends PagedView<WorkspacePageIndicator> * 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(); @@ -3256,6 +3263,10 @@ public class Workspace extends PagedView<WorkspacePageIndicator> } } + public boolean isOverlayShown() { + return mOverlayShown; + } + void moveToDefaultScreen() { int page = DEFAULT_PAGE; if (!workspaceInModalState() && getNextPage() != page) { diff --git a/src/com/android/launcher3/WorkspaceItemInfo.java b/src/com/android/launcher3/WorkspaceItemInfo.java index 5a2373b996..050a8bef75 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 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { * 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 class WorkspaceItemInfo extends ItemInfoWithIcon { 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 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { 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 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { 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 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { 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))) { // 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. diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index fd4df5247e..0c12c60a08 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; 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 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme // 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 class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme } 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 ea9b077c62..293b86722d 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -50,6 +50,7 @@ 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.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.MultiValueAlpha; @@ -297,7 +298,11 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo @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 @@ -623,23 +628,4 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo 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/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java index a0e9dc5d8e..f82e380f88 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java @@ -26,13 +26,15 @@ import android.util.SparseIntArray; 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 com.android.launcher3.views.RecyclerViewFastScroller; 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 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine 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 class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine 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 160042e692..ca8dbebfc2 100644 --- a/src/com/android/launcher3/allapps/AllAppsStore.java +++ b/src/com/android/launcher3/allapps/AllAppsStore.java @@ -39,10 +39,8 @@ public class AllAppsStore { // 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<>(); @@ -80,6 +78,10 @@ public class AllAppsStore { } } + public void disableDeferUpdatesSilently(int flag) { + mDeferUpdatesFlags &= ~flag; + } + public int getDeferUpdatesFlags() { return mDeferUpdatesFlags; } diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 4683893ea0..3836c9fdb4 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -7,6 +7,7 @@ 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 +18,6 @@ import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS; 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 +31,6 @@ import com.android.launcher3.anim.AnimationSuccessListener; 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; @@ -168,10 +167,6 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil @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 +207,14 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil 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 2ad92e16f8..1369441fe1 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -301,11 +301,6 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { } 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(); } diff --git a/src/com/android/launcher3/allapps/FloatingHeaderRow.java b/src/com/android/launcher3/allapps/FloatingHeaderRow.java index 922e4f1a3f..f899587bc0 100644 --- a/src/com/android/launcher3/allapps/FloatingHeaderRow.java +++ b/src/com/android/launcher3/allapps/FloatingHeaderRow.java @@ -46,8 +46,8 @@ public interface FloatingHeaderRow { */ 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 66dced99d3..42a0eee77c 100644 --- a/src/com/android/launcher3/allapps/FloatingHeaderView.java +++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java @@ -27,6 +27,10 @@ import android.view.ViewGroup; 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 com.android.systemui.plugins.PluginListener; 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 @@ public class FloatingHeaderView extends LinearLayout implements 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 b283ff4056..535ef54bc9 100644 --- a/src/com/android/launcher3/allapps/PluginHeaderRow.java +++ b/src/com/android/launcher3/allapps/PluginHeaderRow.java @@ -64,10 +64,10 @@ public class PluginHeaderRow implements FloatingHeaderRow { } @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/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java index 52a896eda1..cd30dea9c0 100644 --- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java +++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java @@ -39,6 +39,8 @@ public class AnimatorSetBuilder { 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 b169cb80be..c45cd85aad 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; @@ -144,7 +145,8 @@ public class Interpolators { 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 +189,13 @@ public class Interpolators { * @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 0000000000..0f34c1e97e --- /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 395fed259a..91a31069c1 100644 --- a/src/com/android/launcher3/anim/SpringObjectAnimator.java +++ b/src/com/android/launcher3/anim/SpringObjectAnimator.java @@ -96,7 +96,10 @@ public class SpringObjectAnimator<T> extends ValueAnimator { } }); - 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 43ae65175f..81c95cbdd4 100644 --- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java +++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java @@ -53,9 +53,6 @@ public class AccessibilityManagerCompat { } 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/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java index 1d19b533a0..1885d8f03d 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java @@ -31,11 +31,14 @@ import android.os.Bundle; 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.PackageUserKey; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import androidx.annotation.NonNull; @@ -167,6 +170,10 @@ public class LauncherAppsCompatVL extends LauncherAppsCompat { @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); } diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java index 7dad7e9052..55df98b48f 100644 --- a/src/com/android/launcher3/compat/PackageInstallerCompat.java +++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java @@ -19,14 +19,25 @@ package com.android.launcher3.compat; 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 abstract class PackageInstallerCompat { } } + 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 abstract class PackageInstallerCompat { 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 a34ca50ebf..879d963c7c 100644 --- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java +++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java @@ -21,17 +21,21 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionCallback; import android.content.pm.PackageInstaller.SessionInfo; +import android.content.pm.PackageManager; import android.os.Handler; -import android.os.Process; 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,11 +43,13 @@ import java.util.HashMap; 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; @@ -51,6 +57,7 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { 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(); @@ -59,22 +66,57 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { mWorker = new Handler(LauncherModel.getWorkerLooper()); mInstaller.registerSessionCallback(mCallback, mWorker); mLauncherApps = LauncherAppsCompat.getInstance(context); + mPromiseIconIds = IntSet.wrap(IntArray.wrap(Utilities.getIntArrayFromString( + 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) { @@ -95,6 +137,30 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { } } + /** + * 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 +173,31 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { 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,14 +214,18 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { @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()); + mActiveSessions.put(session.getSessionId(), + new PackageUserKey(session.getAppPackageName(), getUserHandle(session))); + addSessionInfoToCache(session, getUserHandle(session)); LauncherAppState app = LauncherAppState.getInstanceNoCreate(); if (app != null) { app.getModel().updateSessionDisplayInfo(session.getAppPackageName()); @@ -165,7 +247,7 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { 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 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { } 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 45639e0a43..025087b9d8 100644 --- a/src/com/android/launcher3/config/BaseFlags.java +++ b/src/com/android/launcher3/config/BaseFlags.java @@ -18,20 +18,16 @@ package com.android.launcher3.config; 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 androidx.annotation.VisibleForTesting; import com.android.launcher3.Utilities; +import com.android.launcher3.uioverrides.TogglableFlag; import java.util.ArrayList; import java.util.List; import java.util.SortedMap; @@ -41,11 +37,9 @@ import java.util.TreeMap; * 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 @@ abstract class BaseFlags { // 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; @@ -105,18 +104,22 @@ abstract class BaseFlags { "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 APP_SEARCH_IMPROVEMENTS = new TogglableFlag( + "APP_SEARCH_IMPROVEMENTS", false, + "Adds localized title and keyword search and ranking"); + 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); } } @@ -132,27 +135,30 @@ abstract class BaseFlags { 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 +168,22 @@ abstract class BaseFlags { 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 +192,11 @@ abstract class BaseFlags { 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 +213,8 @@ abstract class BaseFlags { return "TogglableFlag{" + "key=" + key + ", " + "defaultValue=" + defaultValue + ", " + + "overriddenDefaultValue=" + getOverridenDefaultValue(defaultValue) + ", " + + "currentValue=" + currentValue + ", " + "description=" + description + "}"; } @@ -213,9 +225,9 @@ abstract class BaseFlags { 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 +239,10 @@ abstract class BaseFlags { 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 5ecb186500..54e5322bd5 100644 --- a/src/com/android/launcher3/config/FlagTogglerPrefUi.java +++ b/src/com/android/launcher3/config/FlagTogglerPrefUi.java @@ -26,12 +26,13 @@ import android.view.MenuItem; 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 @@ public final class FlagTogglerPrefUi { @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 @@ public final class FlagTogglerPrefUi { // 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 @@ public final class FlagTogglerPrefUi { /** * 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 @@ public final class FlagTogglerPrefUi { } } - 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 a72089d7ca..a2dcbf87d7 100644 --- a/src/com/android/launcher3/dragndrop/AddItemActivity.java +++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java @@ -153,16 +153,6 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener 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 +169,18 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener 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; } diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java index c719c1c986..1b08723f63 100644 --- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java +++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java @@ -137,9 +137,6 @@ public abstract class BaseItemDragListener extends InternalStateHandler implemen @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 72a1abb833..b72fd988a3 100644 --- a/src/com/android/launcher3/dragndrop/DragController.java +++ b/src/com/android/launcher3/dragndrop/DragController.java @@ -474,10 +474,6 @@ public class DragController implements DragDriver.EventListener, TouchController } 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 +488,8 @@ public class DragController implements DragDriver.EventListener, TouchController 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))); - } - if (mIsInPreDrag && mOptions.preDragCondition != null && mOptions.preDragCondition.shouldStartDrag(mDistanceSinceScroll)) { - if (TestProtocol.sDebugTracing) { - android.util.Log.d(TestProtocol.NO_DRAG_TAG, - "handleMoveEvent 2"); - } callOnDragStart(); } } @@ -545,10 +527,6 @@ public class DragController implements DragDriver.EventListener, TouchController * 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 +579,9 @@ public class DragController implements DragDriver.EventListener, TouchController } 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 84fc94dd25..01e0f923c1 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 @@ public abstract class DragDriver { 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 6ba015b2e4..b59164ae0a 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.CellLayout; 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 @@ public class DragLayer extends BaseDragLayer<Launcher> { // 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 @@ public class DragLayer extends BaseDragLayer<Launcher> { 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(); } @@ -529,25 +532,39 @@ public class DragLayer extends BaseDragLayer<Launcher> { @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); + mOverviewScrim.onInsetsChanged(insets); } public WorkspaceAndHotseatScrim getScrim() { - return mScrim; + return mWorkspaceScrim; + } + + public OverviewScrim getOverviewScrim() { + return mOverviewScrim; } @Override diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java index 0c5a1fc5a8..d8a1f99516 100644 --- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java +++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java @@ -28,6 +28,8 @@ import android.graphics.drawable.Drawable; 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; @@ -66,15 +68,19 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable { 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 diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 2ef6d707e3..f22b533380 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -516,7 +516,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { - mFolderIcon.setBackgroundVisible(false); + mFolderIcon.setIconVisible(false); mFolderIcon.drawLeaveBehindIfExists(); } @Override @@ -646,7 +646,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo clearFocus(); if (mFolderIcon != null) { mFolderIcon.setVisibility(View.VISIBLE); - mFolderIcon.setBackgroundVisible(true); + mFolderIcon.setIconVisible(true); mFolderIcon.mFolderName.setTextVisibility(true); if (wasAnimated) { mFolderIcon.animateBgShadowAndStroke(); diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 250169cdb2..0e2d4673e4 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -65,6 +65,7 @@ import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.icons.DotRenderer; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.util.Thunk; +import com.android.launcher3.views.IconLabelDotView; import com.android.launcher3.widget.PendingAddShortcutInfo; import java.util.ArrayList; @@ -73,7 +74,7 @@ import java.util.List; /** * 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; @@ -107,6 +108,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { private Alarm mOpenAlarm = new Alarm(); + private boolean mForceHideDot; @ViewDebug.ExportedProperty(category = "launcher", deepExport = true) private FolderDotInfo mDotInfo = new FolderDotInfo(); private DotRenderer mDotRenderer; @@ -409,6 +411,20 @@ public class FolderIcon extends FrameLayout implements FolderListener { 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 +484,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { mBackground.setInvalidateDelegate(this); } - public void setBackgroundVisible(boolean visible) { + @Override + public void setIconVisible(boolean visible) { mBackgroundIsVisible = visible; invalidate(); } @@ -509,7 +526,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { } 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); diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java index 7eb4015bfa..9263a2ac94 100644 --- a/src/com/android/launcher3/graphics/DragPreviewProvider.java +++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java @@ -29,6 +29,7 @@ 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; @@ -87,6 +88,9 @@ public class DragPreviewProvider { 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; diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java index c9566cb145..288749fa7e 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.Process; 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.icons.BitmapInfo; 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<>(); diff --git a/src/com/android/launcher3/graphics/OverviewScrim.java b/src/com/android/launcher3/graphics/OverviewScrim.java new file mode 100644 index 0000000000..d707403ed2 --- /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 0000000000..5c14f8d20f --- /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 c0aa75f288..6740fa16e5 100644 --- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java +++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java @@ -40,34 +40,19 @@ import android.util.DisplayMetrics; 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 @@ public class WorkspaceAndHotseatScrim implements 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 @@ public class WorkspaceAndHotseatScrim implements 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 @@ public class WorkspaceAndHotseatScrim implements 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 @@ public class WorkspaceAndHotseatScrim implements mBottomMask = mTopScrim == null ? null : createDitheredAlphaMask(); mHideSysUiScrim = mTopScrim == null; - view.addOnAttachStateChangeListener(this); onExtractedColorsChanged(mWallpaperColorInfo); } @@ -176,7 +150,7 @@ public class WorkspaceAndHotseatScrim implements canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE); } - canvas.drawColor(setColorAlphaBound(mFullScrimColor, mScrimAlpha)); + super.draw(canvas); canvas.restore(); } @@ -190,11 +164,8 @@ public class WorkspaceAndHotseatScrim implements 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,24 @@ public class WorkspaceAndHotseatScrim implements } } + 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(); + } + public void onInsetsChanged(Rect insets) { mDrawTopScrim = mTopScrim != null && insets.top > 0; mDrawBottomScrim = mBottomMask != null && !mLauncher.getDeviceProfile().isVerticalBarLayout(); } - 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); + super.onViewAttachedToWindow(view); if (mTopScrim != null) { IntentFilter filter = new IntentFilter(ACTION_SCREEN_OFF); @@ -235,7 +206,7 @@ public class WorkspaceAndHotseatScrim implements @Override public void onViewDetachedFromWindow(View view) { - mWallpaperColorInfo.removeOnChangeListener(this); + super.onViewDetachedFromWindow(view); if (mTopScrim != null) { mRoot.getContext().unregisterReceiver(mReceiver); } @@ -248,10 +219,7 @@ public class WorkspaceAndHotseatScrim implements 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 +259,6 @@ public class WorkspaceAndHotseatScrim implements } } - 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/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index 648445e40c..abff237e8b 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -29,6 +29,8 @@ import android.os.Process; 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; @@ -36,10 +38,11 @@ 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 +53,6 @@ import com.android.launcher3.util.Preconditions; import java.util.function.Supplier; -import androidx.annotation.NonNull; - /** * Cache of application icons. Icons can be made from any thread. */ @@ -75,11 +76,11 @@ public class IconCache extends BaseIconCache { super(context, LauncherFiles.APP_ICONS_DB, LauncherModel.getWorkerLooper(), inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */); mComponentWithLabelCachingLogic = new ComponentCachingLogic(context); - mLauncherActivityInfoCachingLogic = new LauncherActivtiyCachingLogic(this); + 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 @@ -237,7 +238,8 @@ public class IconCache extends BaseIconCache { @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 index 7c996339bd..f9a94daf53 100644 --- a/src/com/android/launcher3/icons/LauncherActivtiyCachingLogic.java +++ b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java @@ -20,14 +20,23 @@ import android.content.Context; 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> { - - private final IconCache mCache; +/** + * Caching logic for LauncherActivityInfo. + */ +public class LauncherActivityCachingLogic + implements CachingLogic<LauncherActivityInfo>, ResourceBasedOverride { - 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 class LauncherActivtiyCachingLogic implements CachingLogic<LauncherActivi 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/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java index 9b9543ec34..cad95b0d24 100644 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ b/src/com/android/launcher3/logging/StatsLogManager.java @@ -40,5 +40,7 @@ public class StatsLogManager implements ResourceBasedOverride { 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 647f255af3..b02a0504ff 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 @@ public class StatsLogUtils { } 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 bd785a107b..c72b07a7f6 100644 --- a/src/com/android/launcher3/logging/UserEventDispatcher.java +++ b/src/com/android/launcher3/logging/UserEventDispatcher.java @@ -115,6 +115,7 @@ public class UserEventDispatcher implements ResourceBasedOverride { protected InstantAppResolver mInstantAppResolver; private boolean mAppOrTaskLaunch; private UserEventDelegate mDelegate; + private boolean mPreviousHomeGesture; // APP_ICON SHORTCUT WIDGET // -------------------------------------------------------------- @@ -186,6 +187,14 @@ public class UserEventDispatcher implements ResourceBasedOverride { 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) { LauncherEvent event = newLauncherEvent(newCommandAction(command), srcTarget); if (command == Action.Command.STOP) { @@ -399,11 +408,22 @@ public class UserEventDispatcher implements ResourceBasedOverride { 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; @@ -426,6 +446,7 @@ public class UserEventDispatcher implements ResourceBasedOverride { ev.actionDurationMillis); log += "\n\n"; Log.d(TAG, log); + return; } 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 ed0d470801..7d4f2f7225 100644 --- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java +++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java @@ -16,6 +16,8 @@ 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; @@ -32,6 +34,8 @@ import com.android.launcher3.LauncherModel.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; @@ -85,6 +89,10 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask { } } + 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 +109,36 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask { 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); + if (sessionInfo == null) { + List<LauncherActivityInfo> activities = launcherApps + .getActivityList(packageName, item.user); + if (activities != null && !activities.isEmpty()) { + // App was installed while launcher was in the background. + itemInfo = new AppInfo(app.getContext(), activities.get(0), item.user) + .makeWorkspaceItem(); + WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo; + wii.title = ""; + wii.applyFrom(app.getIconCache().getDefaultIcon(item.user)); + app.getIconCache().getTitleAndIcon(wii, + ((WorkspaceItemInfo) itemInfo).usingLowResIcon()); + } else { + // Session was cancelled, do not add. + continue; + } + } else { + workspaceInfo.setInstallProgress((int) sessionInfo.getProgress()); + } + } + // Add the shortcut to the db getModelWriter().addItemToDatabase(itemInfo, LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, diff --git a/src/com/android/launcher3/model/AppLaunchTracker.java b/src/com/android/launcher3/model/AppLaunchTracker.java index 1613d47b9f..29a46cfa5c 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 class AppLaunchTracker implements ResourceBasedOverride { 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) { } diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java index 1149b553f6..a0b7177630 100644 --- a/src/com/android/launcher3/model/FirstScreenBroadcast.java +++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java @@ -26,6 +26,7 @@ import com.android.launcher3.ItemInfo; 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; @@ -34,6 +35,8 @@ import java.util.List; 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 @@ -60,7 +63,7 @@ public class FirstScreenBroadcast { private final MultiHashMap<String, String> mPackagesForInstaller; - public FirstScreenBroadcast(HashMap<String, SessionInfo> sessionInfoForPackage) { + public FirstScreenBroadcast(HashMap<PackageUserKey, SessionInfo> sessionInfoForPackage) { mPackagesForInstaller = getPackagesForInstaller(sessionInfoForPackage); } @@ -69,11 +72,13 @@ public class FirstScreenBroadcast { * 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 faecc067e4..783e908e30 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.provider.LauncherDbUtils.SQLiteTransaction; 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 @@ public class GridSizeMigrationTask { .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 1a03b77714..1c39d1f23c 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -227,7 +227,7 @@ public class LoaderCursor extends CursorWrapper { 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 0138572d0c..7593a3371a 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -19,6 +19,7 @@ package com.android.launcher3.model; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED; +import static com.android.launcher3.compat.PackageInstallerCompat.getUserHandle; import static com.android.launcher3.model.LoaderResults.filterCurrentWorkspaceItems; import android.appwidget.AppWidgetProviderInfo; @@ -49,8 +50,8 @@ import com.android.launcher3.LauncherAppState; 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; @@ -61,7 +62,7 @@ import com.android.launcher3.folder.FolderIconPreviewVerifier; 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; @@ -72,6 +73,7 @@ import com.android.launcher3.util.ComponentKey; 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 +198,7 @@ public class LoaderTask implements Runnable { IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler(); setIgnorePackages(updateHandler); updateHandler.updateIcons(allActivityList, - new LauncherActivtiyCachingLogic(mApp.getIconCache()), + LauncherActivityCachingLogic.newInstance(mApp.getContext()), mApp.getModel()::onPackageIconsUpdated); // Take a break @@ -281,8 +283,9 @@ public class LoaderTask implements Runnable { 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<>(); @@ -419,9 +422,10 @@ public class LoaderTask implements Runnable { // 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, @@ -536,7 +540,8 @@ public class LoaderTask implements Runnable { } 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 { @@ -630,8 +635,10 @@ public class LoaderTask implements Runnable { 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); diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java index 5f6d1281bd..9fcab38870 100644 --- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java +++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java @@ -18,7 +18,6 @@ package com.android.launcher3.model; 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; @@ -56,7 +55,7 @@ public class PackageInstallStateChangedTask extends BaseModelUpdateTask { 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 diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index c37ed99522..4428c8e6df 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -43,6 +43,7 @@ import com.android.launcher3.icons.BitmapInfo; 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; @@ -55,6 +56,8 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import static com.android.launcher3.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON; + /** * Handles updates due to changes in package manager (app installed/updated/removed) * or when a user availability changes. @@ -85,6 +88,10 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { @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(); @@ -99,7 +106,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { 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); @@ -227,8 +234,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { isTargetValid = LauncherAppsCompat.getInstance(context) .isActivityEnabledForProfile(cn, mUser); } - if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON) - && !isTargetValid) { + if (si.hasStatusFlag(FLAG_AUTOINSTALL_ICON)) { if (updateWorkspaceItemIntent(context, si, packageName)) { infoUpdated = true; } else if (si.hasPromiseIconUi()) { diff --git a/src/com/android/launcher3/notification/NotificationKeyData.java b/src/com/android/launcher3/notification/NotificationKeyData.java index 5050457b9b..a1917ecb00 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 @@ import androidx.annotation.NonNull; 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 @@ public class NotificationKeyData { 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/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index 9719a1892f..15fb4cea6f 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -36,7 +36,6 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; -import android.util.Log; import android.util.Pair; import android.view.MotionEvent; import android.view.View; @@ -53,7 +52,6 @@ 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 +63,12 @@ import com.android.launcher3.notification.NotificationInfo; 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; @@ -201,7 +198,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, return null; } ItemInfo itemInfo = (ItemInfo) icon.getTag(); - if (!DeepShortcutManager.supportsShortcuts(itemInfo)) { + if (!ShortcutUtil.supportsShortcuts(itemInfo)) { return null; } @@ -300,7 +297,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, } mLauncher.getDragController().addDragListener(this); - mOriginalIcon.forceHideDot(true); + mOriginalIcon.setForceHideDot(true); // All views are added. Animate layout from now on. setLayoutTransition(new LayoutTransition()); @@ -449,11 +446,6 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, @Override public boolean shouldStartDrag(double distanceDragged) { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.NO_DRAG_TAG, - "createPreDragCondition().shouldStartDrag " + distanceDragged + ", " - + mStartDragThreshold); - } return distanceDragged > mStartDragThreshold; } @@ -568,14 +560,14 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, 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() { super.closeComplete(); mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible()); - mOriginalIcon.forceHideDot(false); + mOriginalIcon.setForceHideDot(false); } @Override diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java index 2d301ac008..4612b2a474 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.notification.NotificationListener; 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 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan 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 class PopupDataProvider implements NotificationListener.NotificationsChan } public int getShortcutCountForItem(ItemInfo info) { - if (!DeepShortcutManager.supportsShortcuts(info)) { + if (!ShortcutUtil.supportsDeepShortcuts(info)) { return 0; } ComponentName component = info.getTargetComponent(); @@ -167,17 +173,26 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan 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 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan 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 41ab4df7bf..5a5fbabacf 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.AbstractFloatingView; 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 563f3b3c65..78bd81b464 100644 --- a/src/com/android/launcher3/popup/SystemShortcut.java +++ b/src/com/android/launcher3/popup/SystemShortcut.java @@ -131,6 +131,7 @@ public abstract class SystemShortcut<T extends BaseDraggingActivity> extends Ite @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)); diff --git a/src/com/android/launcher3/popup/SystemShortcutFactory.java b/src/com/android/launcher3/popup/SystemShortcutFactory.java index 516fafad54..37a209289e 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 com.android.launcher3.util.ResourceBasedOverride; 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; diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java index 3c0c5fddea..d643a0b493 100644 --- a/src/com/android/launcher3/provider/RestoreDbTask.java +++ b/src/com/android/launcher3/provider/RestoreDbTask.java @@ -173,12 +173,6 @@ public class RestoreDbTask { 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"); } diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java index d2e196138c..790a2e8448 100644 --- a/src/com/android/launcher3/testing/TestInformationHandler.java +++ b/src/com/android/launcher3/testing/TestInformationHandler.java @@ -15,8 +15,13 @@ */ package com.android.launcher3.testing; +import static android.graphics.Bitmap.Config.ARGB_8888; + 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; @@ -28,6 +33,7 @@ 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 +47,7 @@ public class TestInformationHandler implements ResourceBasedOverride { protected DeviceProfile mDeviceProfile; protected LauncherAppState mLauncherAppState; protected Launcher mLauncher; + private static LinkedList mLeaks; public void init(Context context) { mContext = context; @@ -74,6 +81,11 @@ public class TestInformationHandler implements ResourceBasedOverride { break; } + case TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED: { + response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, true); + break; + } + case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING: TestProtocol.sDebugTracing = true; break; @@ -107,7 +119,37 @@ public class TestInformationHandler implements ResourceBasedOverride { } 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; + } } return response; } -} +}
\ No newline at end of file diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java index 6ffc2d9cd1..232a764e01 100644 --- a/src/com/android/launcher3/testing/TestProtocol.java +++ b/src/com/android/launcher3/testing/TestProtocol.java @@ -66,16 +66,24 @@ public final class TestProtocol { "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_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 ALL_APPS_UPON_RECENTS = "b/139941530"; + public static final String STABLE_STATE_MISMATCH = "b/140311911"; } diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index 6f53140eab..c5ba5bab6a 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.LauncherState.OVERVIEW; 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.Utilities; 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; @@ -57,8 +55,6 @@ import com.android.launcher3.util.TouchController; public abstract class AbstractStateChangeTouchController implements TouchController, SwipeDetector.Listener { - private static final String TAG = "ASCTouchController"; - // 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; @@ -73,6 +69,7 @@ public abstract class AbstractStateChangeTouchController protected final SwipeDetector.Direction mSwipeDirection; private boolean mNoIntercept; + private boolean mIsLogContainerSet; protected int mStartContainerType; protected LauncherState mStartState; @@ -118,9 +115,6 @@ public abstract class AbstractStateChangeTouchController @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) { @@ -150,9 +144,6 @@ public abstract class AbstractStateChangeTouchController return false; } - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onControllerInterceptTouchEvent 2 "); - } onControllerTouchEvent(ev); return mDetector.isDraggingOrSettling(); } @@ -190,7 +181,7 @@ public abstract class AbstractStateChangeTouchController /** * 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 @@ public abstract class AbstractStateChangeTouchController @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 @@ public abstract class AbstractStateChangeTouchController 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) { @@ -376,9 +370,6 @@ public abstract class AbstractStateChangeTouchController @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"); - } final int logAction = fling ? Touch.FLING : Touch.SWIPE; boolean blockedFling = fling && mFlingBlockCheck.isBlocked(); @@ -413,8 +404,8 @@ public abstract class AbstractStateChangeTouchController duration = 0; startProgress = 1; } else { - startProgress = Utilities.boundToRange( - progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f); + startProgress = Utilities.boundToRange(progress + + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f); duration = SwipeDetector.calculateDuration(velocity, endProgress - Math.max(progress, 0)) * durationMultiplier; } @@ -431,8 +422,8 @@ public abstract class AbstractStateChangeTouchController duration = 0; startProgress = 0; } else { - startProgress = Utilities.boundToRange( - progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f); + startProgress = Utilities.boundToRange(progress + + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f); duration = SwipeDetector.calculateDuration(velocity, Math.min(progress, 1) - endProgress) * durationMultiplier; } @@ -515,9 +506,6 @@ public abstract class AbstractStateChangeTouchController } 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,16 +519,16 @@ public abstract class AbstractStateChangeTouchController shouldGoToTargetState = !reachedTarget; } if (shouldGoToTargetState) { - if (targetState != mStartState) { - logReachedState(logAction, targetState); - } - mLauncher.getStateManager().goToState(targetState, false /* animated */); + goToTargetState(targetState, logAction); + } + } - if (com.android.launcher3.testing.TestProtocol.sDebugTracing) { - android.util.Log.e( - TestProtocol.NO_ALLAPPS_EVENT_TAG, "onSwipeInteractionCompleted 2"); - } + 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) { @@ -563,9 +551,6 @@ public abstract class AbstractStateChangeTouchController } 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/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java index 85f763d095..03493a5389 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.Launcher.REQUEST_RECONFIGURE_APPWIDGET; 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.LauncherAppWidgetInfo; 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 @@ import com.android.launcher3.widget.WidgetAddFlowHandler; */ public class ItemClickHandler { + private static final String TAG = ItemClickHandler.class.getSimpleName(); + /** * Instance used for click handling on items */ @@ -68,28 +77,12 @@ public class ItemClickHandler { } 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 @@ public class ItemClickHandler { 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 @@ public class ItemClickHandler { 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 @@ public class ItemClickHandler { (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 @@ public class ItemClickHandler { 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/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java index 3d454046a5..3777a41ad8 100644 --- a/src/com/android/launcher3/touch/SwipeDetector.java +++ b/src/com/android/launcher3/touch/SwipeDetector.java @@ -158,9 +158,6 @@ public class SwipeDetector { // 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); } @@ -168,9 +165,6 @@ public class SwipeDetector { 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 */); @@ -181,11 +175,6 @@ public class SwipeDetector { } 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() { @@ -324,15 +313,9 @@ public class SwipeDetector { 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) { diff --git a/src/com/android/launcher3/touch/TouchEventTranslator.java b/src/com/android/launcher3/touch/TouchEventTranslator.java deleted file mode 100644 index 3fcda90848..0000000000 --- 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/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java new file mode 100644 index 0000000000..7719f084d7 --- /dev/null +++ b/src/com/android/launcher3/util/DefaultDisplay.java @@ -0,0 +1,166 @@ +/* + * 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 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.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, new Handler(UiThreadHelper.getBackgroundLooper())); + } + + @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; + + 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); + } + + 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/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java index 2ee0328597..e185a31990 100644 --- a/src/com/android/launcher3/util/MainThreadInitializedObject.java +++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java @@ -18,12 +18,13 @@ package com.android.launcher3.util; import android.content.Context; import android.os.Looper; +import androidx.annotation.VisibleForTesting; + import com.android.launcher3.MainThreadExecutor; +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. */ @@ -60,6 +61,14 @@ public class MainThreadInitializedObject<T> { 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/PackageUserKey.java b/src/com/android/launcher3/util/PackageUserKey.java index 1ce2822109..aa11968e41 100644 --- a/src/com/android/launcher3/util/PackageUserKey.java +++ b/src/com/android/launcher3/util/PackageUserKey.java @@ -3,6 +3,8 @@ package com.android.launcher3.util; import android.os.UserHandle; import android.service.notification.StatusBarNotification; +import androidx.annotation.Nullable; + import com.android.launcher3.ItemInfo; import com.android.launcher3.shortcuts.DeepShortcutManager; @@ -15,7 +17,9 @@ public class PackageUserKey { 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 +31,7 @@ public class PackageUserKey { 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 +42,8 @@ public class PackageUserKey { * @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/ShortcutUtil.java b/src/com/android/launcher3/util/ShortcutUtil.java new file mode 100644 index 0000000000..af99713a1a --- /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/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java index f948beb8d2..a4518bae3c 100644 --- a/src/com/android/launcher3/views/AbstractSlideInView.java +++ b/src/com/android/launcher3/views/AbstractSlideInView.java @@ -153,15 +153,15 @@ public abstract class AbstractSlideInView extends AbstractFloatingView } 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 15f2724708..c08b659313 100644 --- a/src/com/android/launcher3/views/BaseDragLayer.java +++ b/src/com/android/launcher3/views/BaseDragLayer.java @@ -20,8 +20,7 @@ import static android.view.MotionEvent.ACTION_CANCEL; 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.content.Context; @@ -152,8 +151,6 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext> } private TouchController findControllerToHandleTouch(MotionEvent ev) { - if (shouldDisableGestures(ev)) return null; - AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity); if (topView != null && topView.onControllerInterceptTouchEvent(ev)) { return topView; @@ -223,17 +220,13 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext> // 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 +236,6 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext> } 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 +245,6 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext> @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 +263,10 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext> } 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; diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index ab4b576bf9..f728a67764 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -129,6 +129,7 @@ public class FloatingIconView extends View implements private final Launcher mLauncher; private final int mBlurSizeOutline; + private final boolean mIsRtl; private boolean mIsVerticalBarLayout = false; private boolean mIsAdaptiveIcon = false; @@ -174,6 +175,7 @@ public class FloatingIconView extends View implements 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 +215,10 @@ public class FloatingIconView extends View implements 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 +328,18 @@ public class FloatingIconView extends View implements 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 +523,11 @@ public class FloatingIconView extends View implements } 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); @@ -551,11 +563,6 @@ public class FloatingIconView extends View implements */ private void checkIconResult(View originalView, boolean isOpening) { 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"); @@ -567,7 +574,7 @@ public class FloatingIconView extends View implements setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge, mIconLoadResult.iconOffset); if (isOpening) { - originalView.setVisibility(INVISIBLE); + hideOriginalView(originalView); } } else { mIconLoadResult.onIconLoaded = () -> { @@ -578,15 +585,26 @@ public class FloatingIconView extends View implements 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); + if (isOpening) { + // Delay swapping views until the icon is loaded to prevent a flash. + hideOriginalView(originalView); + } }; mLoadIconSignal = cancellationSignal; } } } + private void hideOriginalView(View originalView) { + if (originalView instanceof BubbleTextView) { + ((BubbleTextView) originalView).setIconVisible(false); + ((BubbleTextView) originalView).setForceHideDot(true); + } else { + originalView.setVisibility(INVISIBLE); + } + } + private void setBackgroundDrawableBounds(float scale) { sTmpRect.set(mFinalDrawableBounds); Utilities.scaleRectAboutCenter(sTmpRect, scale); @@ -656,8 +674,7 @@ public class FloatingIconView extends View implements canvas.restoreToCount(count); } - public void onListenerViewClosed() { - // Fast finish here. + public void fastFinish() { if (mEndRunnable != null) { mEndRunnable.run(); mEndRunnable = null; @@ -704,7 +721,7 @@ public class FloatingIconView extends View implements */ @UiThread public static IconLoadResult fetchIcon(Launcher l, View v, ItemInfo info, boolean isOpening) { - IconLoadResult result = new IconLoadResult(); + IconLoadResult result = new IconLoadResult(info); new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> { RectF position = new RectF(); getLocationBoundsForView(l, v, isOpening, position); @@ -733,10 +750,13 @@ public class FloatingIconView extends View implements // 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; @@ -757,14 +777,19 @@ public class FloatingIconView extends View implements 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); @@ -792,38 +817,33 @@ public class FloatingIconView extends View implements } }); - 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); } }); - } 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 +898,15 @@ public class FloatingIconView extends View implements } 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 0000000000..057caafe78 --- /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 63f742768f..465df448e3 100644 --- a/src/com/android/launcher3/views/OptionsPopupView.java +++ b/src/com/android/launcher3/views/OptionsPopupView.java @@ -18,10 +18,8 @@ package com.android.launcher3.views; 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.OnClickListener; 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 com.android.launcher3.widget.WidgetsFullSheet; 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 class OptionsPopupView extends ArrowPopup } 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 c36011745c..da1df3f899 100644 --- a/src/com/android/launcher3/views/ScrimView.java +++ b/src/com/android/launcher3/views/ScrimView.java @@ -18,14 +18,14 @@ package com.android.launcher3.views; 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.View; 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.userevent.nano.LauncherLogProto.ControlType; 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 @@ -325,7 +327,7 @@ public class ScrimView extends View implements Insettable, OnChangeListener, if (enabled) { stateManager.addStateListener(this); - handleStateChangedComplete(mLauncher.getStateManager().getState()); + handleStateChangedComplete(stateManager.getState()); } else { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); } @@ -437,7 +439,24 @@ public class ScrimView extends View implements Insettable, OnChangeListener, } 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_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java index 6b6f70d7b6..09b1890491 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.Bundle; 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; @@ -63,13 +61,6 @@ public class DeepShortcutManager { 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; } @@ -89,8 +80,9 @@ public class DeepShortcutManager { * 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 List<ShortcutInfo> queryForShortcutsContainer(@Nullable ComponentName activity, UserHandle user) { + if (activity == null) return Collections.EMPTY_LIST; return query(ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC, activity.getPackageName(), activity, null, user); } diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java index e9dc800e3c..bd6ea502e4 100644 --- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java +++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java @@ -57,7 +57,7 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { } @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 0000000000..60f12d82a7 --- /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 5cc64dc9aa..467ae02d5c 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.DeviceProfile; 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,7 @@ public class UiFactory { public static void clearSwipeSharedState(boolean finishAnimation) {} + public static Person[] getPersons(ShortcutInfo si) { + return Utilities.EMPTY_PERSON_ARRAY; + } } diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml index 61c7306e57..c6f55a7178 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"/> diff --git a/tests/OWNERS b/tests/OWNERS index 046d871163..02e8ebcaba 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 0000000000..efbd9c99c3 --- /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/testcomponent/TestCommandReceiver.java b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java index fa23b8d5b9..6a6916eec3 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.Bundle; 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 class TestCommandReceiver extends ContentProvider { 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,12 @@ public class TestCommandReceiver extends ContentProvider { killBackgroundProcesses(arg); return null; } + + case GET_SYSTEM_HEALTH_MESSAGE: { + final Bundle response = new Bundle(); + response.putString("result", TestHelpers.getSystemHealthMessage(getContext())); + return response; + } } return super.call(method, arg, extras); } @@ -122,7 +130,8 @@ public class TestCommandReceiver extends ContentProvider { // 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/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 4a0ca5c245..dc890bb005 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -17,14 +17,13 @@ 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 org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import static java.lang.System.exit; -import android.app.Instrumentation; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -39,9 +38,7 @@ import android.util.Log; 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,13 +46,16 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherState; +import com.android.launcher3.LauncherStateManager; import com.android.launcher3.MainThreadExecutor; -import com.android.launcher3.ResourceUtils; 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.PackageManagerHelper; import com.android.launcher3.util.Wait; import com.android.launcher3.util.rule.FailureWatcher; import com.android.launcher3.util.rule.LauncherActivityRule; @@ -86,8 +86,7 @@ public abstract class AbstractLauncherUiTest { 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(); @@ -103,7 +102,16 @@ public abstract class AbstractLauncherUiTest { } catch (RemoteException e) { throw new RuntimeException(e); } - if (TestHelpers.isInLauncherProcess()) Utilities.enableRunningInTestHarnessForTests(); + if (TestHelpers.isInLauncherProcess()) { + Utilities.enableRunningInTestHarnessForTests(); + mLauncher.setSystemHealthSupplier(() -> TestCommandReceiver.callCommand( + TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE).getString("result")); + mLauncher.setOnSettledStateAction( + containerType -> executeOnLauncher( + launcher -> + checkLauncherIntegrity(launcher, containerType))); + } + mLauncher.enableDebugTracing(); } protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule(); @@ -116,6 +124,23 @@ public abstract class AbstractLauncherUiTest { 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) @@ -162,46 +187,14 @@ public abstract class AbstractLauncherUiTest { } } - 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 +268,12 @@ public abstract class AbstractLauncherUiTest { // 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 +282,20 @@ public abstract class AbstractLauncherUiTest { // 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 +344,27 @@ public abstract class AbstractLauncherUiTest { } 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)); } 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)); + } + + private void startIntent(Intent intent, BySelector selector) { + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + 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 +397,68 @@ public abstract class AbstractLauncherUiTest { 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 58c74cef16..a76b4a4886 100644 --- a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java +++ b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java @@ -72,7 +72,7 @@ public class DefaultLayoutProviderTest extends AbstractLauncherUiTest { writeLayout(new LauncherLayoutBuilder().atHotseat(0).putApp(SETTINGS_APP, SETTINGS_APP)); // Launch the home activity - mActivityMonitor.startLauncher(); + mDevice.pressHome(); waitForModelLoaded(); mLauncher.getWorkspace().getHotseatAppIcon(getSettingsApp().getLabel().toString()); @@ -88,7 +88,7 @@ public class DefaultLayoutProviderTest extends AbstractLauncherUiTest { info.getComponent().getClassName(), 2, 2)); // Launch the home activity - mActivityMonitor.startLauncher(); + mDevice.pressHome(); waitForModelLoaded(); // Verify widget present @@ -105,7 +105,7 @@ public class DefaultLayoutProviderTest extends AbstractLauncherUiTest { .build()); // Launch the home activity - mActivityMonitor.startLauncher(); + mDevice.pressHome(); waitForModelLoaded(); 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 0f36292f90..80bb3edddd 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.runner.Description; 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 @@ class PortraitLandscapeRunner implements TestRule { 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 c3168f812e..c2a3c1c524 100644 --- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java +++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java @@ -190,7 +190,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { 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 +206,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { 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 +216,8 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { 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 +305,8 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { 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 +333,8 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { 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(); } diff --git a/tests/src/com/android/launcher3/ui/TestViewHelpers.java b/tests/src/com/android/launcher3/ui/TestViewHelpers.java index d13d319525..d0df66485e 100644 --- a/tests/src/com/android/launcher3/ui/TestViewHelpers.java +++ b/tests/src/com/android/launcher3/ui/TestViewHelpers.java @@ -18,28 +18,15 @@ package com.android.launcher3.ui; 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 @@ import java.util.function.Function; 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 @@ public class TestViewHelpers { 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 c93c20a616..d9edc35874 100644 --- a/tests/src/com/android/launcher3/ui/WorkTabTest.java +++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java @@ -53,8 +53,8 @@ public class WorkTabTest extends AbstractLauncherUiTest { @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 5eb5f19928..3f35a3a73a 100644 --- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java @@ -27,20 +27,18 @@ import android.view.View; 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; @@ -71,58 +69,37 @@ public class AddConfigWidgetTest extends AbstractLauncherUiTest { } @Test - // Convert test to TAPL b/131116002 + @PortraitLandscape + @org.junit.Ignore 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 + @org.junit.Ignore 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 0061568cce..1edce22ec6 100644 --- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java @@ -15,24 +15,20 @@ */ 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; @@ -49,42 +45,29 @@ public class AddWidgetTest extends AbstractLauncherUiTest { @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 + @org.junit.Ignore + 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()); } } diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java index d36126bb17..e6348d9c02 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.tapl.Workspace; 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.Rule; 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,7 +270,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest { resetLoaderState(); // Launch the home activity - mActivityMonitor.startLauncher(); + mDevice.pressHome(); waitForModelLoaded(); } @@ -326,9 +329,12 @@ public class BindWidgetTest extends AbstractLauncherUiTest { 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 6122daec2c..07129ddd95 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 @@ public class RequestPinItemTest extends AbstractLauncherUiTest { 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/Wait.java b/tests/src/com/android/launcher3/util/Wait.java index 593cce832b..899686bd61 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 class Wait { } 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 @@ public class Wait { } 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 eef2f24baa..cdda0f0dcc 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 @@ import java.io.IOException; 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 @@ public class FailureWatcher extends TestWatcher { @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 2aba7a56de..62fe26d13b 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 class LauncherActivityRule implements TestRule { } 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 @@ public class LauncherActivityRule implements TestRule { } @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/tapl/AndroidManifest.xml b/tests/tapl/AndroidManifest.xml index 0207e2be6f..1065446827 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 7f561a2af3..03d1600783 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 class AddToHomeScreenPrompt { 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 9ff354a7eb..f070280ea2 100644 --- a/tests/tapl/com/android/launcher3/tapl/AllApps.java +++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java @@ -120,7 +120,7 @@ public class AllApps extends LauncherInstrumentation.VisibleContainer { 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); } diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java index c9eaf276d7..bcce8ef579 100644 --- a/tests/tapl/com/android/launcher3/tapl/Background.java +++ b/tests/tapl/com/android/launcher3/tapl/Background.java @@ -54,12 +54,12 @@ public class Background extends LauncherInstrumentation.VisibleContainer { "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 +112,6 @@ public class Background extends LauncherInstrumentation.VisibleContainer { mLauncher.waitForSystemUiObject("recent_apps").click(); break; } - mLauncher.getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING); } protected String getSwipeHeightRequestName() { diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java index ace49e9cea..25e6e8c8f7 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 @@ import java.util.List; * 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 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { @Override protected LauncherInstrumentation.ContainerType getContainerType() { - return LauncherInstrumentation.ContainerType.BASE_OVERVIEW; + return LauncherInstrumentation.ContainerType.FALLBACK_OVERVIEW; } /** @@ -49,9 +52,10 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { 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, 1, new Rect(leftMargin, 0, 0, 0), 20); verifyActiveContainer(); } } @@ -70,7 +74,7 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { 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 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { 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, 1, new Rect(0, 0, rightMargin, 0), 20); verifyActiveContainer(); } } diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java index d4bdafa764..df80a51fd7 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.BySelector; 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 @@ abstract class Launchable { 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 @@ abstract class Launchable { /** * 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 @@ abstract class Launchable { 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 a7e6336199..15615fc6b6 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -72,6 +72,8 @@ 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.Supplier; /** * The main tapl object. The only object that can be explicitly constructed by the using code. It @@ -85,8 +87,8 @@ public final class LauncherInstrumentation { // 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 +124,7 @@ public final class LauncherInstrumentation { 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,6 +134,9 @@ public final class LauncherInstrumentation { private int mExpectedRotation = Surface.ROTATION_0; private final Uri mTestProviderUri; private final Deque<String> mDiagnosticContext = new LinkedList<>(); + private Supplier<String> mSystemHealthSupplier; + + private Consumer<ContainerType> mOnSettledStateAction; /** * Constructs the root of TAPL hierarchy. You get all other objects from it. @@ -173,6 +178,7 @@ public final class LauncherInstrumentation { 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 +212,7 @@ public final class LauncherInstrumentation { 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); @@ -263,10 +269,79 @@ public final class LauncherInstrumentation { } } + 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(Supplier<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.get() + : TestHelpers.getSystemHealthMessage(getContext()); + } + 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 +368,7 @@ public final class LauncherInstrumentation { } } - 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 +406,33 @@ public final class LauncherInstrumentation { } 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 +469,7 @@ public final class LauncherInstrumentation { return waitForLauncherObject(OVERVIEW_RES_ID); } - case BASE_OVERVIEW: { + case FALLBACK_OVERVIEW: { return waitForFallbackLauncherObject(OVERVIEW_RES_ID); } case BACKGROUND: { @@ -390,6 +486,18 @@ public final class LauncherInstrumentation { } } + 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 { @@ -571,13 +679,6 @@ public final class LauncherInstrumentation { } @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 +715,12 @@ public final class LauncherInstrumentation { @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 +750,10 @@ public final class LauncherInstrumentation { return mDevice.getLauncherPackageName(); } + boolean isFallbackOverview() { + return !getOverviewPackageName().equals(getLauncherPackageName()); + } + @NonNull public UiDevice getDevice() { return mDevice; @@ -658,8 +763,7 @@ public final class LauncherInstrumentation { 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))); @@ -684,7 +788,7 @@ public final class LauncherInstrumentation { startX = endX = rect.centerX(); final int vertCenter = rect.centerY(); final float halfGestureHeight = rect.height() * percent / 2.0f; - startY = (int) (vertCenter - halfGestureHeight); + startY = (int) (vertCenter - halfGestureHeight) + 1; endY = (int) (vertCenter + halfGestureHeight); } break; @@ -692,10 +796,26 @@ public final class LauncherInstrumentation { startX = endX = rect.centerX(); final int vertCenter = rect.centerY(); final float halfGestureHeight = rect.height() * percent / 2.0f; - startY = (int) (vertCenter + halfGestureHeight); + 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() * percent / 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() * percent / 2.0f; + startX = (int) (horizCenter + halfGestureWidth) - 1; + endX = (int) (horizCenter - halfGestureWidth); + } + break; default: fail("Unsupported direction"); return; @@ -711,6 +831,7 @@ public final class LauncherInstrumentation { // 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); @@ -760,6 +881,7 @@ public final class LauncherInstrumentation { } 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 +934,7 @@ public final class LauncherInstrumentation { 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 +948,25 @@ public final class LauncherInstrumentation { 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 058831f180..da68da3ba6 100644 --- a/tests/tapl/com/android/launcher3/tapl/Overview.java +++ b/tests/tapl/com/android/launcher3/tapl/Overview.java @@ -19,9 +19,9 @@ package com.android.launcher3.tapl; 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 @@ public final class Overview extends BaseOverview { // 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 641c413538..91f0fc4c83 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 final class OverviewTask { */ 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 93554d2452..a089a527ef 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.Intent; 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,70 @@ public class TestHelpers { } 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) { + DropBoxManager dropbox = (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE); + Assert.assertNotNull("Unable access the DropBoxManager service", dropbox); + + long timestamp = System.currentTimeMillis() - 5 * 60000; + 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) { + try { + StringBuilder errors = new StringBuilder(); + + final String[] labels = { + "system_app_anr", + "system_app_crash", + "system_app_native_crash", + "system_app_wtf", + "system_server_anr", + "system_server_crash", + "system_server_native_crash", + "system_server_watchdog", + }; + + for (String label : labels) { + final String crash = checkCrash(context, label); + 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 128789dbc1..1b6d8c4a6e 100644 --- a/tests/tapl/com/android/launcher3/tapl/Widget.java +++ b/tests/tapl/com/android/launcher3/tapl/Widget.java @@ -18,7 +18,16 @@ package com.android.launcher3.tapl; 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 94003be919..7d308afb99 100644 --- a/tests/tapl/com/android/launcher3/tapl/Widgets.java +++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java @@ -16,6 +16,13 @@ package com.android.launcher3.tapl; +import static org.junit.Assert.fail; + +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; @@ -25,7 +32,8 @@ import com.android.launcher3.ResourceUtils; * All widgets container. */ public final class Widgets extends LauncherInstrumentation.VisibleContainer { - private static final int FLING_SPEED = 1500; + private static final Rect MARGINS = new Rect(100, 100, 100, 100); + private static final int FLING_STEPS = 10; Widgets(LauncherInstrumentation launcher) { super(launcher); @@ -40,11 +48,7 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer { "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, 1f, MARGINS, FLING_STEPS); try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung forward")) { verifyActiveContainer(); } @@ -60,10 +64,7 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer { "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, 1f, MARGINS, FLING_STEPS); try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung back")) { verifyActiveContainer(); } @@ -75,4 +76,34 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer { protected LauncherInstrumentation.ContainerType getContainerType() { return LauncherInstrumentation.ContainerType.WIDGETS; } + + public Widget getWidget(String labelText) { + final int margin = ResourceUtils.getNavbarSize( + ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1; + final UiObject2 widgetsContainer = verifyActiveContainer(); + widgetsContainer.setGestureMargins(0, 0, 0, margin); + + final Point displaySize = mLauncher.getRealDisplaySize(); + + int i = 0; + final BySelector selector = By.clazz("android.widget.TextView").text(labelText); + + for (; ; ) { + final UiObject2 label = mLauncher.tryWaitForLauncherObject(selector, 300); + if (label != null) { + 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 - margin) { + return new Widget(mLauncher, widget); + } + } + + if (++i > 40) fail("Too many attempts"); + mLauncher.scroll(widgetsContainer, Direction.DOWN, 0.7f, MARGINS, 50); + } + } } diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java index b01b6f363c..510ea14091 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 com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL; 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; @@ -40,6 +41,7 @@ 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 +69,6 @@ public final class Workspace extends Home { "switchToAllApps: swipeHeight = " + swipeHeight + ", slop = " + mLauncher.getTouchSlop()); - mLauncher.getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING); mLauncher.swipeToState( start.x, start.y, @@ -75,7 +76,6 @@ public final class Workspace extends Home { 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 +110,13 @@ public final class Workspace extends Home { */ @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))); + } } /** @@ -144,20 +147,19 @@ public final class Workspace extends Home { @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 +174,6 @@ public final class Workspace extends Home { 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 +182,9 @@ public final class Workspace extends Home { */ 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, 1f, + new Rect(0, 0, mLauncher.getEdgeSensitivityWidth(), 0), + FLING_STEPS); verifyActiveContainer(); } @@ -193,9 +194,9 @@ public final class Workspace extends Home { */ 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, 1f, + new Rect(mLauncher.getEdgeSensitivityWidth(), 0, 0, 0), + FLING_STEPS); verifyActiveContainer(); } |