From 202014db2f596ddebefdebebc947cd4e5085a451 Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Mon, 4 Mar 2019 14:53:53 -0800 Subject: [PATCH 01/68] App icon transitions with app window in overview Fixes: 123641382 Test: Swipe up and hold from app when SWIPE_HOME is enabled or swipe up from app when it's not Change-Id: I1bd35b1b96d66a3996f9b24c9a7e896535fa1ca0 --- .../WindowTransformSwipeHandler.java | 4 +- .../quickstep/views/LiveTileOverlay.java | 69 +++++++++++++++++++ .../android/quickstep/views/RecentsView.java | 14 ++++ .../com/android/quickstep/views/TaskView.java | 11 ++- 4 files changed, 96 insertions(+), 2 deletions(-) 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 7dc58a58c3..7bea5935c4 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -415,6 +415,7 @@ public class WindowTransformSwipeHandler }); mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper); mRecentsView.setClipAnimationHelper(mClipAnimationHelper); + mRecentsView.setLiveTileOverlay(mLiveTileOverlay); mActivity.getRootView().getOverlay().add(mLiveTileOverlay); mStateCallback.setState(STATE_LAUNCHER_PRESENT); @@ -822,6 +823,7 @@ public class WindowTransformSwipeHandler setShelfState(ShelfAnimState.CANCEL, LINEAR, 0); duration = Math.max(MIN_OVERSHOOT_DURATION, duration); } else if (endTarget == RECENTS) { + mLiveTileOverlay.startIconAnimation(); mRecentsAnimationWrapper.enableInputProxy(); if (mRecentsView != null) { duration = Math.max(duration, mRecentsView.getScroller().getDuration()); @@ -1172,7 +1174,7 @@ public class WindowTransformSwipeHandler mActivityControlHelper.onSwipeUpComplete(mActivity); // Animate the first icon. - mRecentsView.animateUpRunningTaskIconScale(); + mRecentsView.animateUpRunningTaskIconScale(mLiveTileOverlay.cancelIconAnimation()); mRecentsView.setSwipeDownShouldLaunchApp(true); RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java index ab2b90ff10..a83879738d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java @@ -1,5 +1,11 @@ package com.android.quickstep.views; +import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; +import static com.android.launcher3.anim.Interpolators.LINEAR; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; @@ -9,16 +15,37 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; +import android.util.FloatProperty; + +import com.android.launcher3.anim.Interpolators; public class LiveTileOverlay extends Drawable { + private static final long ICON_ANIM_DURATION = 120; + + private static final FloatProperty PROGRESS = + new FloatProperty("progress") { + @Override + public void setValue(LiveTileOverlay liveTileOverlay, float progress) { + liveTileOverlay.setIconAnimationProgress(progress); + } + + @Override + public Float get(LiveTileOverlay liveTileOverlay) { + return liveTileOverlay.mIconAnimationProgress; + } + }; + private final Paint mPaint = new Paint(); private Rect mBoundsRect = new Rect(); private RectF mCurrentRect; private float mCornerRadius; + private Drawable mIcon; + private Animator mIconAnimator; private boolean mDrawEnabled = true; + private float mIconAnimationProgress = 0f; public LiveTileOverlay() { mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); @@ -35,6 +62,33 @@ public class LiveTileOverlay extends Drawable { invalidateSelf(); } + public void setIcon(Drawable icon) { + mIcon = icon; + } + + public void startIconAnimation() { + if (mIconAnimator != null) { + mIconAnimator.cancel(); + } + // This animator must match the icon part of {@link TaskView#FOCUS_TRANSITION} animation. + mIconAnimator = ObjectAnimator.ofFloat(this, PROGRESS, 1); + mIconAnimator.setDuration(ICON_ANIM_DURATION).setInterpolator(LINEAR); + mIconAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mIconAnimator = null; + } + }); + mIconAnimator.start(); + } + + public float cancelIconAnimation() { + if (mIconAnimator != null) { + mIconAnimator.cancel(); + } + return mIconAnimationProgress; + } + public void setDrawEnabled(boolean drawEnabled) { if (mDrawEnabled != drawEnabled) { mDrawEnabled = drawEnabled; @@ -46,6 +100,16 @@ public class LiveTileOverlay extends Drawable { public void draw(Canvas canvas) { if (mCurrentRect != null && mDrawEnabled) { canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint); + if (mIcon != null && mIconAnimationProgress > 0f) { + canvas.save(); + float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, 0f, + 1f).getInterpolation(mIconAnimationProgress); + canvas.translate(mCurrentRect.centerX() - mIcon.getBounds().width() / 2 * scale, + mCurrentRect.top - mIcon.getBounds().height() / 2 * scale); + canvas.scale(scale, scale); + mIcon.draw(canvas); + canvas.restore(); + } } } @@ -59,4 +123,9 @@ public class LiveTileOverlay extends Drawable { public int getOpacity() { return PixelFormat.TRANSLUCENT; } + + private void setIconAnimationProgress(float progress) { + mIconAnimationProgress = progress; + invalidateSelf(); + } } 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 3e0e8ae583..2583ffb0d9 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 @@ -278,6 +278,7 @@ public abstract class RecentsView extends PagedView impl private final int mEmptyMessagePadding; private boolean mShowEmptyMessage; private Layout mEmptyTextLayout; + private LiveTileOverlay mLiveTileOverlay; private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener = (inMultiWindowMode) -> { @@ -855,10 +856,15 @@ public abstract class RecentsView extends PagedView impl } public void animateUpRunningTaskIconScale() { + animateUpRunningTaskIconScale(0); + } + + public void animateUpRunningTaskIconScale(float startProgress) { mRunningTaskIconScaledDown = false; TaskView firstTask = getRunningTaskView(); if (firstTask != null) { firstTask.animateIconScaleAndDimIntoView(); + firstTask.setIconScaleAnimStartProgress(startProgress); } } @@ -1567,6 +1573,14 @@ public abstract class RecentsView extends PagedView impl mClipAnimationHelper = clipAnimationHelper; } + public void setLiveTileOverlay(LiveTileOverlay liveTileOverlay) { + mLiveTileOverlay = liveTileOverlay; + } + + public void updateLiveTileIcon(Drawable icon) { + mLiveTileOverlay.setIcon(icon); + } + public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) { if (mRecentsAnimationWrapper == null) { if (onFinishComplete != null) { 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 419a66674d..9eec584a27 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 @@ -156,7 +156,8 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { private float mZoomScale; private float mFullscreenProgress; - private Animator mIconAndDimAnimator; + private ObjectAnimator mIconAndDimAnimator; + private float mIconScaleAnimStartProgress = 0; private float mFocusTransitionProgress = 1; private boolean mShowScreenshot; @@ -317,6 +318,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { mIconLoadRequest = iconCache.updateIconInBackground(mTask, (task) -> { setIcon(task.icon); + if (isRunningTask()) { + getRecentsView().updateLiveTileIcon(task.icon); + } mDigitalWellBeingToast.initialize( mTask, (saturation, contentDescription) -> { @@ -380,11 +384,16 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { mIconView.setScaleY(scale); } + public void setIconScaleAnimStartProgress(float startProgress) { + mIconScaleAnimStartProgress = startProgress; + } + public void animateIconScaleAndDimIntoView() { if (mIconAndDimAnimator != null) { mIconAndDimAnimator.cancel(); } mIconAndDimAnimator = ObjectAnimator.ofFloat(this, FOCUS_TRANSITION, 1); + mIconAndDimAnimator.setCurrentFraction(mIconScaleAnimStartProgress); mIconAndDimAnimator.setDuration(DIM_ANIM_DURATION).setInterpolator(LINEAR); mIconAndDimAnimator.addListener(new AnimatorListenerAdapter() { @Override From 371ea051d19c26398779db9e43bacb47097e1858 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 6 Mar 2019 15:38:32 -0800 Subject: [PATCH 02/68] Fixing launcher UI not reapplied properly when IDP changed as a result of display changes > Removing unnecessary check when binding model data as its safe to bind old data, we should just not persist it (that check is already there in model writer) > Adding additional check in model bind to skip old bind if there are multiple binds Bug: 126259547 Bug: 118441555 Change-Id: Ic250d2af13797d3e70c6e1fc70da65ebe6ab8d70 --- src/com/android/launcher3/BubbleTextView.java | 8 --- src/com/android/launcher3/ItemInfo.java | 2 - src/com/android/launcher3/Launcher.java | 22 ++++---- src/com/android/launcher3/LauncherModel.java | 52 ------------------- .../launcher3/model/BaseLoaderResults.java | 7 +++ .../android/launcher3/model/ModelWriter.java | 30 +++++------ 6 files changed, 35 insertions(+), 86 deletions(-) diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index d96855ecfe..d75006e893 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -275,14 +275,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, mLongPressHelper.setLongPressTimeoutFactor(longPressTimeoutFactor); } - @Override - public void setTag(Object tag) { - if (tag != null) { - LauncherModel.checkItemInfo((ItemInfo) tag); - } - super.setTag(tag); - } - @Override public void refreshDrawableState() { if (!mIgnorePressedStateChange) { diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java index a130604779..134e116063 100644 --- a/src/com/android/launcher3/ItemInfo.java +++ b/src/com/android/launcher3/ItemInfo.java @@ -114,8 +114,6 @@ public class ItemInfo { ItemInfo(ItemInfo info) { copyFrom(info); - // tempdebug: - LauncherModel.checkItemInfo(this); } public void copyFrom(ItemInfo info) { diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index d820448879..8d029e71b6 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -378,14 +378,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) { - mUserEventDispatcher = null; - initDeviceProfile(mDeviceProfile.inv); - dispatchDeviceProfileChanged(); - reapplyUi(); - mDragLayer.recreateControllers(); - - // TODO: We can probably avoid rebind when only screen size changed. - rebindModel(); + onIdpChanged(mDeviceProfile.inv); } mOldConfig.setTo(newConfig); @@ -410,8 +403,19 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, @Override public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) { + onIdpChanged(idp); + } + + private void onIdpChanged(InvariantDeviceProfile idp) { + mUserEventDispatcher = null; + initDeviceProfile(idp); - getRootView().dispatchInsets(); + dispatchDeviceProfileChanged(); + reapplyUi(); + mDragLayer.recreateControllers(); + + // TODO: We can probably avoid rebind when only screen size changed. + rebindModel(); } private void initDeviceProfile(InvariantDeviceProfile idp) { diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index b1664f1103..c559f2ba1e 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -34,7 +34,6 @@ import android.util.Pair; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.model.AddWorkspaceItemsTask; @@ -207,57 +206,6 @@ public class LauncherModel extends BroadcastReceiver hasVerticalHotseat, verifyChanges); } - static void checkItemInfoLocked( - final int itemId, final ItemInfo item, StackTraceElement[] stackTrace) { - ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId); - if (modelItem != null && item != modelItem) { - // If it is a release build on a release device, check all the data is consistent as - // we don't want to crash non-dev users. - if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_DOGFOOD_BUILD && - modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) { - if (modelItem.title.toString().equals(item.title.toString()) && - modelItem.getIntent().filterEquals(item.getIntent()) && - modelItem.id == item.id && - modelItem.itemType == item.itemType && - modelItem.container == item.container && - modelItem.screenId == item.screenId && - modelItem.cellX == item.cellX && - modelItem.cellY == item.cellY && - modelItem.spanX == item.spanX && - modelItem.spanY == item.spanY) { - // For all intents and purposes, this is the same object - return; - } - } - - // the modelItem needs to match up perfectly with item if our model is - // to be consistent with the database-- for now, just require - // modelItem == item or the equality check above - String msg = "item: " + ((item != null) ? item.toString() : "null") + - "modelItem: " + - ((modelItem != null) ? modelItem.toString() : "null") + - "Error: ItemInfo passed to checkItemInfo doesn't match original"; - RuntimeException e = new RuntimeException(msg); - if (stackTrace != null) { - e.setStackTrace(stackTrace); - } - throw e; - } - } - - static void checkItemInfo(final ItemInfo item) { - final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); - final int itemId = item.id; - Runnable r = new Runnable() { - public void run() { - synchronized (sBgDataModel) { - checkItemInfoLocked(itemId, item, stackTrace); - } - } - }; - runOnWorkerThread(r); - } - /** * Set this as the current Launcher activity object for the loader. */ diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java index 210f744492..97cf267d34 100644 --- a/src/com/android/launcher3/model/BaseLoaderResults.java +++ b/src/com/android/launcher3/model/BaseLoaderResults.java @@ -61,6 +61,8 @@ public abstract class BaseLoaderResults { protected final WeakReference mCallbacks; + private int mMyBindingId; + public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel, AllAppsList allAppsList, int pageToBindFirst, WeakReference callbacks) { mUiExecutor = new MainThreadExecutor(); @@ -94,6 +96,7 @@ public abstract class BaseLoaderResults { appWidgets.addAll(mBgDataModel.appWidgets); orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens()); mBgDataModel.lastBindId++; + mMyBindingId = mBgDataModel.lastBindId; } final int currentScreen; @@ -285,6 +288,10 @@ public abstract class BaseLoaderResults { protected void executeCallbacksTask(CallbackTask task, Executor executor) { executor.execute(() -> { + if (mMyBindingId != mBgDataModel.lastBindId) { + Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind"); + return; + } Callbacks callbacks = mCallbacks.get(); if (callbacks != null) { task.execute(callbacks); diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java index ac5076c11b..daf99e9cac 100644 --- a/src/com/android/launcher3/model/ModelWriter.java +++ b/src/com/android/launcher3/model/ModelWriter.java @@ -37,6 +37,7 @@ import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherSettings.Settings; import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.ContentWriter; import com.android.launcher3.util.ItemInfoMatcher; @@ -112,19 +113,18 @@ public class ModelWriter { ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId); if (modelItem != null && item != modelItem) { // check all the data is consistent - if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) { - ShortcutInfo modelShortcut = (ShortcutInfo) modelItem; - ShortcutInfo shortcut = (ShortcutInfo) item; - if (modelShortcut.title.toString().equals(shortcut.title.toString()) && - modelShortcut.intent.filterEquals(shortcut.intent) && - modelShortcut.id == shortcut.id && - modelShortcut.itemType == shortcut.itemType && - modelShortcut.container == shortcut.container && - modelShortcut.screenId == shortcut.screenId && - modelShortcut.cellX == shortcut.cellX && - modelShortcut.cellY == shortcut.cellY && - modelShortcut.spanX == shortcut.spanX && - modelShortcut.spanY == shortcut.spanY) { + if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_DOGFOOD_BUILD && + modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) { + if (modelItem.title.toString().equals(item.title.toString()) && + modelItem.getIntent().filterEquals(item.getIntent()) && + modelItem.id == item.id && + modelItem.itemType == item.itemType && + modelItem.container == item.container && + modelItem.screenId == item.screenId && + modelItem.cellX == item.cellX && + modelItem.cellY == item.cellY && + modelItem.spanX == item.spanX && + modelItem.spanY == item.spanY) { // For all intents and purposes, this is the same object return; } @@ -310,7 +310,7 @@ public class ModelWriter { /** * Delete operations tracked using {@link #enqueueDeleteRunnable} will only be called * if {@link #commitDelete} is called. Note that one of {@link #commitDelete()} or - * {@link #abortDelete()} MUST be called after this method, or else all delete + * {@link #abortDelete} MUST be called after this method, or else all delete * operations will remain uncommitted indefinitely. */ public void prepareToUndoDelete() { @@ -325,7 +325,7 @@ public class ModelWriter { /** * If {@link #prepareToUndoDelete} has been called, we store the Runnable to be run when - * {@link #commitDelete()} is called (or abandoned if {@link #abortDelete()} is called). + * {@link #commitDelete()} is called (or abandoned if {@link #abortDelete} is called). * Otherwise, we run the Runnable immediately. */ private void enqueueDeleteRunnable(Runnable r) { From 1c22a2c7a2f97e7c2e435e380c2eeca02d168f69 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Thu, 7 Mar 2019 00:34:01 +0000 Subject: [PATCH 03/68] Revert "Revert "Temporarily make TouchInteractionService direct boot unaware"" This reverts commit 84062d4b5de5168b388eeb4c1102e2637efcd945. Reason for revert: We want TouchInteractionService to be direct boot unaware until engineers can flash to a build where sysui expects it to be direct boot aware (sorry for the churn...). Change-Id: I27c00257819751af52df40467d5a63dc106e1917 --- quickstep/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml index 62d0500160..0992849980 100644 --- a/quickstep/AndroidManifest.xml +++ b/quickstep/AndroidManifest.xml @@ -39,7 +39,7 @@ + android:directBootAware="false" > From 2bc6b934acedf96ae9e01beb8e9cedcdb4e72842 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Thu, 7 Mar 2019 15:24:46 -0800 Subject: [PATCH 04/68] Add launcher support for restoring work profile items onto workspace. Bug: 111301511 Change-Id: I40decf4c763401c97afcc2bfa7954a1f3a03f536 --- .../android/launcher3/LauncherProvider.java | 12 +- .../android/launcher3/model/LoaderTask.java | 6 +- .../launcher3/provider/RestoreDbTask.java | 127 +++++++++++++++--- .../launcher3/provider/RestoreDbTaskTest.java | 18 ++- 4 files changed, 138 insertions(+), 25 deletions(-) diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index fb33694343..ce73229286 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -20,6 +20,7 @@ import static com.android.launcher3.provider.LauncherDbUtils.dropTable; import static com.android.launcher3.provider.LauncherDbUtils.tableExists; import android.annotation.TargetApi; +import android.app.backup.BackupManager; import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetManager; import android.content.ComponentName; @@ -150,7 +151,7 @@ public class LauncherProvider extends ContentProvider { mOpenHelper = new DatabaseHelper(getContext(), mListenerHandler); if (RestoreDbTask.isPending(getContext())) { - if (!RestoreDbTask.performRestore(mOpenHelper)) { + if (!RestoreDbTask.performRestore(mOpenHelper, new BackupManager(getContext()))) { mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase()); } // Set is pending to false irrespective of the result, so that it doesn't get @@ -542,6 +543,7 @@ public class LauncherProvider extends ContentProvider { * The class is subclassed in tests to create an in-memory db. */ public static class DatabaseHelper extends NoLocaleSQLiteHelper implements LayoutParserCallback { + private final BackupManager mBackupManager; private final Handler mWidgetHostResetHandler; private final Context mContext; private int mMaxItemId = -1; @@ -571,6 +573,7 @@ public class LauncherProvider extends ContentProvider { super(context, tableName, SCHEMA_VERSION); mContext = context; mWidgetHostResetHandler = widgetHostResetHandler; + mBackupManager = new BackupManager(mContext); } protected void initIds() { @@ -620,9 +623,12 @@ public class LauncherProvider extends ContentProvider { Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit(); } + public long getSerialNumberForUser(UserHandle user) { + return UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user); + } + public long getDefaultUserSerial() { - return UserManagerCompat.getInstance(mContext).getSerialNumberForUser( - Process.myUserHandle()); + return getSerialNumberForUser(Process.myUserHandle()); } private void addFavoritesTable(SQLiteDatabase db, boolean optional) { diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 72755762a3..a27e2c2b9c 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -366,13 +366,13 @@ public class LoaderTask implements Runnable { ComponentName cn = intent.getComponent(); targetPkg = cn == null ? intent.getPackage() : cn.getPackageName(); - if (!Process.myUserHandle().equals(c.user)) { + if (allUsers.indexOfValue(c.user) < 0) { if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { - c.markDeleted("Legacy shortcuts are only allowed for default user"); + c.markDeleted("Legacy shortcuts are only allowed for current users"); continue; } else if (c.restoreFlag != 0) { // Don't restore items for other profiles. - c.markDeleted("Restore from managed profile not supported"); + c.markDeleted("Restore from other profiles not supported"); continue; } } diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java index bcca4d8501..040b5e5271 100644 --- a/src/com/android/launcher3/provider/RestoreDbTask.java +++ b/src/com/android/launcher3/provider/RestoreDbTask.java @@ -18,10 +18,15 @@ package com.android.launcher3.provider; import static com.android.launcher3.provider.LauncherDbUtils.dropTable; +import android.app.backup.BackupManager; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.os.Build; +import android.os.UserHandle; +import android.util.LongSparseArray; +import android.util.SparseLongArray; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherProvider.DatabaseHelper; @@ -48,10 +53,10 @@ public class RestoreDbTask { private static final String INFO_COLUMN_NAME = "name"; private static final String INFO_COLUMN_DEFAULT_VALUE = "dflt_value"; - public static boolean performRestore(DatabaseHelper helper) { + public static boolean performRestore(DatabaseHelper helper, BackupManager backupManager) { SQLiteDatabase db = helper.getWritableDatabase(); try (SQLiteTransaction t = new SQLiteTransaction(db)) { - new RestoreDbTask().sanitizeDB(helper, db); + new RestoreDbTask().sanitizeDB(helper, db, backupManager); t.commit(); return true; } catch (Exception e) { @@ -62,20 +67,44 @@ public class RestoreDbTask { /** * Makes the following changes in the provider DB. - * 1. Removes all entries belonging to a managed profile as managed profiles - * cannot be restored. + * 1. Removes all entries belonging to any profiles that were not restored. * 2. Marks all entries as restored. The flags are updated during first load or as * the restored apps get installed. - * 3. If the user serial for primary profile is different than that of the previous device, - * update the entries to the new profile id. + * 3. If the user serial for any restored profile is different than that of the previous + * device, update the entries to the new profile id. */ - private void sanitizeDB(DatabaseHelper helper, SQLiteDatabase db) throws Exception { + private void sanitizeDB(DatabaseHelper helper, SQLiteDatabase db, BackupManager backupManager) + throws Exception { + // Primary user ids + long myProfileId = helper.getDefaultUserSerial(); long oldProfileId = getDefaultProfileId(db); - // Delete all entries which do not belong to the main user - int itemsDeleted = db.delete( - Favorites.TABLE_NAME, "profileId != ?", new String[]{Long.toString(oldProfileId)}); + LongSparseArray oldManagedProfileIds = getManagedProfileIds(db, oldProfileId); + LongSparseArray profileMapping = new LongSparseArray<>(oldManagedProfileIds.size() + + 1); + + // Build mapping of restored profile ids to their new profile ids. + profileMapping.put(oldProfileId, myProfileId); + for (int i = oldManagedProfileIds.size() - 1; i >= 0; --i) { + long oldManagedProfileId = oldManagedProfileIds.keyAt(i); + UserHandle user = getUserForAncestralSerialNumber(backupManager, oldManagedProfileId); + if (user != null) { + long newManagedProfileId = helper.getSerialNumberForUser(user); + profileMapping.put(oldManagedProfileId, newManagedProfileId); + } + } + + // Delete all entries which do not belong to any restored profile(s). + int numProfiles = profileMapping.size(); + String[] profileIds = new String[numProfiles]; + profileIds[0] = Long.toString(oldProfileId); + StringBuilder whereClause = new StringBuilder("profileId != ?"); + for (int i = profileMapping.size() - 1; i >= 1; --i) { + whereClause.append(" AND profileId != ?"); + profileIds[i] = Long.toString(profileMapping.keyAt(i)); + } + int itemsDeleted = db.delete(Favorites.TABLE_NAME, whereClause.toString(), profileIds); if (itemsDeleted > 0) { - FileLog.d(TAG, itemsDeleted + " items belonging to a managed profile, were deleted"); + FileLog.d(TAG, itemsDeleted + " items from unrestored user(s) were deleted"); } // Mark all items as restored. @@ -85,7 +114,7 @@ public class RestoreDbTask { | (keepAllIcons ? ShortcutInfo.FLAG_RESTORE_STARTED : 0)); db.update(Favorites.TABLE_NAME, values, null, null); - // Mark widgets with appropriate restore flag + // Mark widgets with appropriate restore flag. values.put(Favorites.RESTORED, LauncherAppWidgetInfo.FLAG_ID_NOT_VALID | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY | LauncherAppWidgetInfo.FLAG_UI_NOT_READY | @@ -93,21 +122,46 @@ public class RestoreDbTask { db.update(Favorites.TABLE_NAME, values, "itemType = ?", new String[]{Integer.toString(Favorites.ITEM_TYPE_APPWIDGET)}); - long myProfileId = helper.getDefaultUserSerial(); + // Migrate ids. To avoid any overlap, we initially move conflicting ids to a temp location. + // Using Long.MIN_VALUE since profile ids can not be negative, so there will be no overlap. + final long tempLocationOffset = Long.MIN_VALUE; + SparseLongArray tempMigratedIds = new SparseLongArray(profileMapping.size()); + int numTempMigrations = 0; + for (int i = profileMapping.size() - 1; i >= 0; --i) { + long oldId = profileMapping.keyAt(i); + long newId = profileMapping.valueAt(i); + + if (oldId != newId) { + if (profileMapping.indexOfKey(newId) >= 0) { + tempMigratedIds.put(numTempMigrations, newId); + numTempMigrations++; + newId = tempLocationOffset + newId; + } + migrateProfileId(db, oldId, newId); + } + } + + // Migrate ids from their temporary id to their actual final id. + for (int i = tempMigratedIds.size() - 1; i >= 0; --i) { + long newId = tempMigratedIds.valueAt(i); + migrateProfileId(db, tempLocationOffset + newId, newId); + } + if (myProfileId != oldProfileId) { - FileLog.d(TAG, "Changing primary user id from " + oldProfileId + " to " + myProfileId); - migrateProfileId(db, myProfileId); + changeDefaultColumn(db, myProfileId); } } /** - * Updates profile id of all entries and changes the default value for the column. + * Updates profile id of all entries from {@param oldProfileId} to {@param newProfileId}. */ - protected void migrateProfileId(SQLiteDatabase db, long newProfileId) { + protected void migrateProfileId(SQLiteDatabase db, long oldProfileId, long newProfileId) { + FileLog.d(TAG, "Changing profile user id from " + oldProfileId + " to " + newProfileId); // Update existing entries. ContentValues values = new ContentValues(); values.put(Favorites.PROFILE_ID, newProfileId); - db.update(Favorites.TABLE_NAME, values, null, null); + 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;"); @@ -116,6 +170,43 @@ public class RestoreDbTask { dropTable(db, "favorites_old"); } + + /** + * Changes the default value for the column. + */ + protected void changeDefaultColumn(SQLiteDatabase db, long newProfileId) { + 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"); + } + + /** + * Returns a list of the managed profile id(s) used in the favorites table of the provided db. + */ + private LongSparseArray getManagedProfileIds(SQLiteDatabase db, long defaultProfileId) { + LongSparseArray ids = new LongSparseArray<>(); + try (Cursor c = db.rawQuery("SELECT profileId from favorites WHERE profileId != ? " + + "GROUP BY profileId", new String[] {Long.toString(defaultProfileId)})){ + while (c.moveToNext()) { + ids.put(c.getLong(c.getColumnIndex(Favorites.PROFILE_ID)), null); + } + } + return ids; + } + + /** + * Returns a UserHandle of a restored managed profile with the given serial number, or null + * if none found. + */ + private UserHandle getUserForAncestralSerialNumber(BackupManager backupManager, + long ancestralSerialNumber) { + if (Build.VERSION.SDK_INT < 29) { + return null; + } + return backupManager.getUserForAncestralSerialNumber(ancestralSerialNumber); + } + /** * Returns the profile id used in the favorites table of the provided db. */ diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java index babb7315eb..80facc64a5 100644 --- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java +++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java @@ -41,11 +41,27 @@ public class RestoreDbTaskTest { // Verify item add assertEquals(5, getCount(db, "select * from favorites where profileId = 42")); - new RestoreDbTask().migrateProfileId(db, 33); + new RestoreDbTask().migrateProfileId(db, 42, 33); // verify data migrated assertEquals(0, getCount(db, "select * from favorites where profileId = 42")); assertEquals(5, getCount(db, "select * from favorites where profileId = 33")); + } + + @Test + public void testChangeDefaultColumn() throws Exception { + SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase(); + // Add some dummy data + for (int i = 0; i < 5; i++) { + ContentValues values = new ContentValues(); + values.put(Favorites._ID, i); + values.put(Favorites.TITLE, "item " + i); + db.insert(Favorites.TABLE_NAME, null, values); + } + // Verify item add + assertEquals(5, getCount(db, "select * from favorites where profileId = 42")); + + new RestoreDbTask().changeDefaultColumn(db, 33); // Verify default value changed ContentValues values = new ContentValues(); From 2ca0aa7076e696848bb3cc28102f0a1091a570ef Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Fri, 8 Mar 2019 10:58:52 -0800 Subject: [PATCH 05/68] Fix testChangeDefaultColumnTest. I split testMigrateProfileId into two tests: one testing the migration and one testing changing the default column test. Since testChangeDefaultColumnTest does not migrate any profile ids, the correct answer is indeed 1 and not 6. Next time I will be sure to run the tests first :). Change-Id: I7557e198072ed134c8b42d6187afcb93d388c5a1 --- .../src/com/android/launcher3/provider/RestoreDbTaskTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java index 80facc64a5..6fa8d62a17 100644 --- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java +++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java @@ -58,7 +58,7 @@ public class RestoreDbTaskTest { values.put(Favorites.TITLE, "item " + i); db.insert(Favorites.TABLE_NAME, null, values); } - // Verify item add + // Verify default column is 42 assertEquals(5, getCount(db, "select * from favorites where profileId = 42")); new RestoreDbTask().changeDefaultColumn(db, 33); @@ -68,7 +68,7 @@ public class RestoreDbTaskTest { values.put(Favorites._ID, 100); values.put(Favorites.TITLE, "item 100"); db.insert(Favorites.TABLE_NAME, null, values); - assertEquals(6, getCount(db, "select * from favorites where profileId = 33")); + assertEquals(1, getCount(db, "select * from favorites where profileId = 33")); } private int getCount(SQLiteDatabase db, String sql) { From b9ebd812a4ea05fb9f4cd3a556221a289fd8d3d3 Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 4 Mar 2019 10:15:13 -0800 Subject: [PATCH 06/68] Add task thumbnails to the Recents Go loader. This CL adds thumbnails to the content loading for Recents Go so that they are fully loaded before recents show. We utilize the underlying recents model thumbnail cache to make the loading faster. Bug: 114136250 Test: Build Launcher3GoIconRecents Change-Id: Ib21f617e83d307621364c2eeb6fef84aad49814b --- .../com/android/quickstep/TaskListLoader.java | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/go/quickstep/src/com/android/quickstep/TaskListLoader.java b/go/quickstep/src/com/android/quickstep/TaskListLoader.java index 9f359b4c1b..c798cef1ee 100644 --- a/go/quickstep/src/com/android/quickstep/TaskListLoader.java +++ b/go/quickstep/src/com/android/quickstep/TaskListLoader.java @@ -85,34 +85,40 @@ public final class TaskListLoader { } /** - * Loads task content for a list of tasks, including the label and the icon. Uses the list of - * tasks since the last load as a cache for loaded content. + * Loads task content for a list of tasks, including the label, icon, and thumbnail. For content + * that isn't cached, load the content asynchronously in the background. * * @param tasksToLoad list of tasks that need to load their content - * @param onLoadedCallback runnable to run after all tasks have loaded their content + * @param onFullyLoadedCallback runnable to run after all tasks have loaded their content */ private void loadTaskContents(ArrayList tasksToLoad, - @Nullable Runnable onLoadedCallback) { - AtomicInteger loadRequestsCount = new AtomicInteger(0); + @Nullable Runnable onFullyLoadedCallback) { + // Make two load requests per task, one for the icon/title and one for the thumbnail. + AtomicInteger loadRequestsCount = new AtomicInteger(tasksToLoad.size() * 2); + Runnable itemLoadedRunnable = () -> { + if (loadRequestsCount.decrementAndGet() == 0 && onFullyLoadedCallback != null) { + onFullyLoadedCallback.run(); + } + }; for (Task task : tasksToLoad) { + // Load icon and title. int index = mTaskList.indexOf(task); if (index >= 0) { // If we've already loaded the task and have its content then just copy it over. Task loadedTask = mTaskList.get(index); task.titleDescription = loadedTask.titleDescription; task.icon = loadedTask.icon; + itemLoadedRunnable.run(); } else { // Otherwise, load the content in the background. - loadRequestsCount.getAndIncrement(); - mRecentsModel.getIconCache().updateIconInBackground(task, loadedTask -> { - if (loadRequestsCount.decrementAndGet() == 0 && onLoadedCallback != null) { - onLoadedCallback.run(); - } - }); + mRecentsModel.getIconCache().updateIconInBackground(task, + loadedTask -> itemLoadedRunnable.run()); } - } - if (loadRequestsCount.get() == 0 && onLoadedCallback != null) { - onLoadedCallback.run(); + + // Load the thumbnail. May return immediately and synchronously if the thumbnail is + // cached. + mRecentsModel.getThumbnailCache().updateThumbnailInBackground(task, + thumbnail -> itemLoadedRunnable.run()); } } } From e6f3fa47d626290dc46066d032ca4a39daffc45e Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Mon, 4 Mar 2019 09:14:40 -0800 Subject: [PATCH 07/68] Fix bug where folder items preview remain in low res state. - We update the ranks of all folder items after loading, to ensure there are no gaps caused by removed folder items. This also ensures that we load the high resolution icons for all preview items. - FolderIconPreviewVerifier#setFolderInfo was not always called - Init mGridSize with [1, 1] to prevent divide by zero error in case setFolderInfo is not called Bug: 126268196 Change-Id: I856489968665a39303e2922c78cf90f2b3ee6ebb --- src/com/android/launcher3/Launcher.java | 12 ++++++--- .../android/launcher3/folder/FolderIcon.java | 1 + .../folder/FolderIconPreviewVerifier.java | 25 +++++++++++++++---- .../launcher3/folder/FolderPagedView.java | 1 + .../android/launcher3/model/LoaderTask.java | 22 ++++++++-------- 5 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index d820448879..61cd2f10aa 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -488,11 +488,15 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, @Override public void invalidateParent(ItemInfo info) { - FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(getDeviceProfile().inv); - if (verifier.isItemInPreview(info.rank) && (info.container >= 0)) { + if (info.container >= 0) { View folderIcon = getWorkspace().getHomescreenIconByItemId(info.container); - if (folderIcon != null) { - folderIcon.invalidate(); + if (folderIcon instanceof FolderIcon && folderIcon.getTag() instanceof FolderInfo) { + FolderIconPreviewVerifier verifier = + new FolderIconPreviewVerifier(getDeviceProfile().inv); + verifier.setFolderInfo((FolderInfo) folderIcon.getTag()); + if (verifier.isItemInPreview(info.rank)) { + folderIcon.invalidate(); + } } } } diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 67495ea755..71c91488be 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -200,6 +200,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { private void setFolder(Folder folder) { mFolder = folder; mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv); + mPreviewVerifier.setFolderInfo(mFolder.getInfo()); updatePreviewItems(false); } diff --git a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java index 5a27cd43e2..4c84e354db 100644 --- a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java +++ b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java @@ -16,6 +16,8 @@ package com.android.launcher3.folder; +import android.util.Log; + import com.android.launcher3.FolderInfo; import com.android.launcher3.InvariantDeviceProfile; @@ -26,14 +28,20 @@ import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_I */ public class FolderIconPreviewVerifier { + private static final String TAG = "FolderPreviewVerifier"; + private final int mMaxGridCountX; private final int mMaxGridCountY; private final int mMaxItemsPerPage; - private final int[] mGridSize = new int[2]; + private final int[] mGridSize = new int[] { 1, 1 }; + private int mNumItemsInFolder; private int mGridCountX; private boolean mDisplayingUpperLeftQuadrant = false; + /** + * Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work. + */ public FolderIconPreviewVerifier(InvariantDeviceProfile profile) { mMaxGridCountX = profile.numFolderColumns; mMaxGridCountY = profile.numFolderRows; @@ -42,11 +50,14 @@ public class FolderIconPreviewVerifier { public void setFolderInfo(FolderInfo info) { int numItemsInFolder = info.contents.size(); - FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, mMaxGridCountX, - mMaxGridCountY, mMaxItemsPerPage, mGridSize); - mGridCountX = mGridSize[0]; + if (numItemsInFolder != mNumItemsInFolder) { + FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, mMaxGridCountX, + mMaxGridCountY, mMaxItemsPerPage, mGridSize); + mGridCountX = mGridSize[0]; - mDisplayingUpperLeftQuadrant = numItemsInFolder > MAX_NUM_ITEMS_IN_PREVIEW; + mDisplayingUpperLeftQuadrant = numItemsInFolder > MAX_NUM_ITEMS_IN_PREVIEW; + mNumItemsInFolder = numItemsInFolder; + } } /** @@ -62,6 +73,10 @@ public class FolderIconPreviewVerifier { * @return True iff the icon is in the 2x2 upper left quadrant of the Folder. */ public boolean isItemInPreview(int page, int rank) { + if (mGridSize[0] == 1) { + Log.w(TAG, "setFolderInfo not called before checking if item is in preview."); + } + // First page items are laid out such that the first 4 items are always in the upper // left quadrant. For all other pages, we need to check the row and col. if (page > 0 || mDisplayingUpperLeftQuadrant) { diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index 06eaf38bca..d4ce6dd233 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -321,6 +321,7 @@ public class FolderPagedView extends PagedView { FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier( Launcher.getLauncher(getContext()).getDeviceProfile().inv); + verifier.setFolderInfo(mFolder.getInfo()); rank = 0; for (int i = 0; i < itemCount; i++) { View v = list.size() > i ? list.get(i) : null; diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index a27e2c2b9c..2b20b08896 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -340,8 +340,6 @@ public class LoaderTask implements Runnable { Intent intent; String targetPkg; - FolderIconPreviewVerifier verifier = - new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile()); while (!mStopped && c.moveToNext()) { try { if (c.user == null) { @@ -461,8 +459,7 @@ public class LoaderTask implements Runnable { c.markRestored(); } - boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() && - !verifier.isItemInPreview(c.getInt(rankIndex)); + boolean useLowResIcon = !c.isOnWorkspaceOrHotseat(); if (c.restoreFlag != 0) { // Already verified above that user is same as default user @@ -745,24 +742,25 @@ public class LoaderTask implements Runnable { } } + // Sort the folder items, update ranks, and make sure all preview items are high res. FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile()); - // Sort the folder items and make sure all items in the preview are high resolution. for (FolderInfo folder : mBgDataModel.folders) { Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR); verifier.setFolderInfo(folder); + int size = folder.contents.size(); + + // Update ranks here to ensure there are no gaps caused by removed folder items. + // Ranks are the source of truth for folder items, so cellX and cellY can be ignored + // for now. Database will be updated once user manually modifies folder. + for (int rank = 0; rank < size; ++rank) { + ShortcutInfo info = folder.contents.get(rank); + info.rank = rank; - int numItemsInPreview = 0; - for (ShortcutInfo info : folder.contents) { if (info.usingLowResIcon() && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION && verifier.isItemInPreview(info.rank)) { mIconCache.getTitleAndIcon(info, false); - numItemsInPreview++; - } - - if (numItemsInPreview >= MAX_NUM_ITEMS_IN_PREVIEW) { - break; } } } From 7c8fb786fa529c76fc88b8b24dec2d39e225546b Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 8 Mar 2019 17:04:57 -0800 Subject: [PATCH 08/68] Use GestureDetector in WorkspaceTouchListener Replace the current callback logic in WorkspaceTouchListener with the use of GestureDetector. That means, platform api is now going to be used for detecting long press. There now are some unused variables and unneeded code in this file. It will be cleaned up in a follow-up CL. Test: long press on empty home screen Bug: 126596502 Change-Id: I57302b403f7d7118f2156166d8c707ab3f9ccefd --- .../launcher3/touch/WorkspaceTouchListener.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java index 4de082e90e..78017569e0 100644 --- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java +++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java @@ -29,6 +29,7 @@ import android.graphics.Rect; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; +import android.view.GestureDetector; import android.view.View.OnTouchListener; import android.view.ViewConfiguration; @@ -45,7 +46,7 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; /** * Helper class to handle touch on empty space in workspace and show options popup on long press */ -public class WorkspaceTouchListener implements OnTouchListener, Runnable { +public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListener implements OnTouchListener { /** * STATE_PENDING_PARENT_INFORM is the state between longPress performed & the next motionEvent. @@ -66,16 +67,21 @@ public class WorkspaceTouchListener implements OnTouchListener, Runnable { private int mLongPressState = STATE_CANCELLED; + private final GestureDetector mGestureDetector; + public WorkspaceTouchListener(Launcher launcher, Workspace workspace) { mLauncher = launcher; mWorkspace = workspace; // Use twice the touch slop as we are looking for long press which is more // likely to cause movement. mTouchSlop = 2 * ViewConfiguration.get(launcher).getScaledTouchSlop(); + mGestureDetector = new GestureDetector(workspace.getContext(), this); } @Override public boolean onTouch(View view, MotionEvent ev) { + mGestureDetector.onTouchEvent(ev); + int action = ev.getActionMasked(); if (action == ACTION_DOWN) { // Check if we can handle long press. @@ -97,7 +103,6 @@ public class WorkspaceTouchListener implements OnTouchListener, Runnable { if (handleLongPress) { mLongPressState = STATE_REQUESTED; mTouchDownPoint.set(ev.getX(), ev.getY()); - mWorkspace.postDelayed(this, getLongPressTimeout()); } mWorkspace.onTouchEvent(ev); @@ -155,12 +160,11 @@ public class WorkspaceTouchListener implements OnTouchListener, Runnable { } private void cancelLongPress() { - mWorkspace.removeCallbacks(this); mLongPressState = STATE_CANCELLED; } @Override - public void run() { + public void onLongPress(MotionEvent event) { if (mLongPressState == STATE_REQUESTED) { if (canHandleLongPress()) { mLongPressState = STATE_PENDING_PARENT_INFORM; From c1edfa7328899024df1c08b8cb4d2db833800706 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Fri, 8 Mar 2019 16:32:59 -0800 Subject: [PATCH 09/68] Removing unnecessary system RPC for checking flags on every touch event Change-Id: I8116bef952dbd49d1bad6c8844149538a6d0fa6b --- .../android/launcher3/config/BaseFlags.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java index d6e450a8ab..cb239b3de1 100644 --- a/src/com/android/launcher3/config/BaseFlags.java +++ b/src/com/android/launcher3/config/BaseFlags.java @@ -21,6 +21,9 @@ 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; @@ -256,6 +259,16 @@ abstract class BaseFlags { @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); } @@ -274,10 +287,5 @@ abstract class BaseFlags { } return Settings.Global.getInt(contentResolver, getKey(), defaultValue ? 1 : 0) == 1; } - - @Override - public boolean get() { - return getFromStorage(null, getDefaultValue()); - } } } From c0a67af45650a85da99505198d2f1e5047e23e2c Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 1 Mar 2019 17:03:39 -0800 Subject: [PATCH 10/68] Add task item view for Recents Go. Add a task item view for the recycler view to inflate when task data updates. Currently, the adapter only binds the icon and label. Thumbnails will be handled in a separate CL. Bug: 114136250 Test: Manual test Launcher3GoIconRecents Change-Id: Iaff2001fbbf0a3fc6f5781f90b336bee43f9076e --- go/quickstep/res/layout/task_item_view.xml | 37 +++++++++++ .../com/android/quickstep/TaskAdapter.java | 10 +-- .../src/com/android/quickstep/TaskHolder.java | 18 +++--- .../android/quickstep/views/TaskItemView.java | 64 +++++++++++++++++++ 4 files changed, 115 insertions(+), 14 deletions(-) create mode 100644 go/quickstep/res/layout/task_item_view.xml create mode 100644 go/quickstep/src/com/android/quickstep/views/TaskItemView.java diff --git a/go/quickstep/res/layout/task_item_view.xml b/go/quickstep/res/layout/task_item_view.xml new file mode 100644 index 0000000000..90940c4951 --- /dev/null +++ b/go/quickstep/res/layout/task_item_view.xml @@ -0,0 +1,37 @@ + + + + + + diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java index 57cd60a992..d3fd24026c 100644 --- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java +++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java @@ -15,16 +15,17 @@ */ package com.android.quickstep; +import android.view.LayoutInflater; import android.view.ViewGroup; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView.Adapter; +import com.android.launcher3.R; +import com.android.quickstep.views.TaskItemView; import com.android.systemui.shared.recents.model.Task; import java.util.ArrayList; - /** * Recycler view adapter that dynamically inflates and binds {@link TaskHolder} instances with the * appropriate {@link Task} from the recents task list. @@ -42,8 +43,9 @@ public final class TaskAdapter extends Adapter { @Override public TaskHolder onCreateViewHolder(ViewGroup parent, int viewType) { // TODO: Swap in an actual task view here (view w/ icon, label, etc.) - TextView stubView = new TextView(parent.getContext()); - return new TaskHolder(stubView); + TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext()) + .inflate(R.layout.task_item_view, parent, false); + return new TaskHolder(itemView); } @Override diff --git a/go/quickstep/src/com/android/quickstep/TaskHolder.java b/go/quickstep/src/com/android/quickstep/TaskHolder.java index 1ea6d7610f..47398c876e 100644 --- a/go/quickstep/src/com/android/quickstep/TaskHolder.java +++ b/go/quickstep/src/com/android/quickstep/TaskHolder.java @@ -15,10 +15,9 @@ */ package com.android.quickstep; -import android.widget.TextView; - import androidx.recyclerview.widget.RecyclerView.ViewHolder; +import com.android.quickstep.views.TaskItemView; import com.android.systemui.shared.recents.model.Task; /** @@ -27,22 +26,21 @@ import com.android.systemui.shared.recents.model.Task; */ final class TaskHolder extends ViewHolder { - // TODO: Implement the actual task view to be held. - // For now, we just use a simple text view. - private final TextView mStubView; + private final TaskItemView mTaskItemView; - public TaskHolder(TextView stubView) { - super(stubView); - mStubView = stubView; + public TaskHolder(TaskItemView itemView) { + super(itemView); + mTaskItemView = itemView; } /** * Bind task content to the view. This includes the task icon and title as well as binding * input handlers such as which task to launch/remove. * - * @param task the task to bind to the view this + * @param task the task to bind to the view */ public void bindTask(Task task) { - mStubView.setText("Stub task view: " + task.titleDescription); + mTaskItemView.setLabel(task.titleDescription); + mTaskItemView.setIcon(task.icon); } } diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java new file mode 100644 index 0000000000..ce3947d240 --- /dev/null +++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.views; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.launcher3.R; + +/** + * View representing an individual task item with the icon + thumbnail adjacent to the task label. + */ +public final class TaskItemView extends LinearLayout { + + private TextView mLabelView; + private ImageView mIconView; + + public TaskItemView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mLabelView = findViewById(R.id.task_label); + mIconView = findViewById(R.id.task_icon_and_thumbnail); + } + + /** + * Set the label for the task item. + * + * @param label task label + */ + public void setLabel(String label) { + mLabelView.setText(label); + } + + /** + * Set the icon for the task item. + * + * @param icon task icon + */ + public void setIcon(Drawable icon) { + mIconView.setImageDrawable(icon); + // TODO: Add in combination drawable for icon + thumbnail + } +} From 836602e31347fb6c6aa0f793006f10d1b310eb56 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Mon, 11 Feb 2019 15:03:47 -0800 Subject: [PATCH 11/68] Quickly fade in QSB when swiping up when SWIPE_HOME = false Now that the "hook" gesture is supported for old quickstep to switch apps, we need to fade out the QSB so it doesn't overlap the TaskView we are switching to. Bug: 111926330 Change-Id: Ie04c7909f6b96d12c810b438db89eedf8ea5bdeb --- .../uioverrides/BackgroundAppState.java | 10 ++++++ .../LauncherActivityControllerHelper.java | 36 ++++++++++++++----- .../allapps/AllAppsTransitionController.java | 5 ++- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java index 963f1fa493..fdb80da387 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java @@ -20,6 +20,7 @@ import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS; import android.os.RemoteException; import com.android.launcher3.Launcher; import com.android.launcher3.allapps.AllAppsTransitionController; +import com.android.launcher3.config.FeatureFlags; import com.android.quickstep.RecentsModel; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.views.RecentsView; @@ -72,4 +73,13 @@ public class BackgroundAppState extends OverviewState { float scale = (float) appWidth / sTempRect.width(); return new float[] { scale, 0f }; } + + @Override + public int getVisibleElements(Launcher launcher) { + if (FeatureFlags.SWIPE_HOME.get()) { + return super.getVisibleElements(launcher); + } + // Hide shelf content (e.g. QSB) because we fade it in when swiping up. + return ALL_APPS_HEADER_EXTRA; + } } 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 9650a5316a..96f79a8db8 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -23,6 +23,7 @@ import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_DAMPING_RATIO; import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_STIFFNESS; +import static com.android.launcher3.anim.Interpolators.DEACCEL_3; import static com.android.launcher3.anim.Interpolators.LINEAR; import android.animation.Animator; @@ -38,13 +39,20 @@ import android.view.MotionEvent; import android.view.View; import android.view.animation.Interpolator; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.UiThread; + import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherInitListener; import com.android.launcher3.LauncherState; +import com.android.launcher3.LauncherStateManager; +import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.SpringObjectAnimator; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; @@ -61,10 +69,6 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import java.util.function.BiPredicate; import java.util.function.Consumer; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.UiThread; - /** * {@link ActivityControlHelper} for the in-launcher recents. */ @@ -211,7 +215,7 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe : mShelfState == ShelfAnimState.PEEK ? shelfPeekingProgress : shelfOverviewProgress; - mShelfAnim = createShelfAnim(activity, toProgress); + mShelfAnim = createShelfProgressAnim(activity, toProgress); mShelfAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -229,10 +233,10 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe LauncherState fromState, long transitionLength, Consumer callback) { LauncherState endState = OVERVIEW; + DeviceProfile dp = activity.getDeviceProfile(); + long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx); if (wasVisible && fromState != BACKGROUND_APP) { // If a translucent app was launched fom launcher, animate launcher states. - DeviceProfile dp = activity.getDeviceProfile(); - long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx); callback.accept(activity.getStateManager() .createAnimationToNewWorkspace(fromState, endState, accuracy)); return; @@ -245,10 +249,11 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe if (!activity.getDeviceProfile().isVerticalBarLayout() && !FeatureFlags.SWIPE_HOME.get()) { // Don't animate the shelf when SWIPE_HOME is true, because we update it atomically. - Animator shiftAnim = createShelfAnim(activity, + Animator shiftAnim = createShelfProgressAnim(activity, fromState.getVerticalProgress(activity), endState.getVerticalProgress(activity)); anim.play(shiftAnim); + anim.play(createShelfAlphaAnim(activity, endState, accuracy)); } playScaleDownAnim(anim, activity, endState); @@ -265,7 +270,7 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe callback.accept(controller); } - private Animator createShelfAnim(Launcher activity, float ... progressValues) { + private Animator createShelfProgressAnim(Launcher activity, float ... progressValues) { Animator shiftAnim = new SpringObjectAnimator<>(activity.getAllAppsController(), "allAppsSpringFromACH", activity.getAllAppsController().getShiftRange(), SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues); @@ -273,6 +278,19 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe return shiftAnim; } + /** + * Very quickly fade the alpha of shelf content. + */ + private Animator createShelfAlphaAnim(Launcher activity, LauncherState toState, long accuracy) { + AllAppsTransitionController allAppsController = activity.getAllAppsController(); + AnimatorSetBuilder animBuilder = new AnimatorSetBuilder(); + animBuilder.setInterpolator(AnimatorSetBuilder.ANIM_ALL_APPS_FADE, DEACCEL_3); + LauncherStateManager.AnimationConfig config = new LauncherStateManager.AnimationConfig(); + config.duration = accuracy; + allAppsController.setAlphas(toState.getVisibleElements(activity), config, animBuilder); + return animBuilder.build(); + } + /** * Scale down recents from the center task being full screen to being in overview. */ diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 93bf69daf8..a4ecec7b8e 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -188,9 +188,12 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil private void setAlphas(LauncherState toState, AnimationConfig config, AnimatorSetBuilder builder) { + setAlphas(toState.getVisibleElements(mLauncher), config, builder); + } + + public void setAlphas(int visibleElements, AnimationConfig config, AnimatorSetBuilder builder) { PropertySetter setter = config == null ? NO_ANIM_PROPERTY_SETTER : config.getPropertySetter(builder); - int visibleElements = toState.getVisibleElements(mLauncher); boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0; boolean hasContent = (visibleElements & ALL_APPS_CONTENT) != 0; From 28d43c580acd593866a204e7ec9b7a822ca2cf28 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Mon, 11 Mar 2019 10:28:48 -0700 Subject: [PATCH 12/68] Send onGestureStarted() when continuing a quick switch gesture When continuing the gesture, we set mPassedTouchSlop = true in order to make the second gesture responsive/fluid. However, we only call notifyGestureStarted() when passing that slop, meaning we don't call it in this case. Now we call it immediately on touch down when continuing the previous gesture. This cleans up a lot of inconsistent state issues, such as not creating the launcher animation controller as part of the second quick switch gesture. Bug: 111926330 Change-Id: I03c3ed9a8d364fbfdc2d04bafb58a63bf61e2f63 --- .../src/com/android/quickstep/OtherActivityInputConsumer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java index e3afb92dbf..4e010d25e7 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java @@ -230,7 +230,6 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC mTouchSlop) { mPassedTouchSlop = true; - TOUCH_INTERACTION_LOG.addLog("startQuickstep"); if (mIsDeferredDownTarget) { // Deferred gesture, start the animation and gesture tracking once // we pass the actual touch slop @@ -272,6 +271,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC } private void notifyGestureStarted() { + TOUCH_INTERACTION_LOG.addLog("startQuickstep"); if (mInteractionHandler == null) { return; } @@ -310,6 +310,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC if (listenerSet != null) { listenerSet.addListener(handler); mSwipeSharedState.applyActiveRecentsAnimationState(handler); + notifyGestureStarted(); } else { RecentsAnimationListenerSet newListenerSet = mSwipeSharedState.newRecentsAnimationListenerSet(); From 0bd63d11e8ce4aa626892effcf03c9982c00bc58 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Wed, 6 Mar 2019 17:29:29 -0800 Subject: [PATCH 13/68] Add landscape support for DeviceProfile#getItemLocation. Also updated to use better estimates for portrait. Bug: 123900446 Change-Id: I39d9ccec9a936bc0b5800ae77e7f88c4cfec1372 --- src/com/android/launcher3/DeviceProfile.java | 39 +++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 3b054c2be8..7919d29358 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -133,6 +133,7 @@ public class DeviceProfile { private final Rect mInsets = new Rect(); public final Rect workspacePadding = new Rect(); private final Rect mHotseatPadding = new Rect(); + // When true, nav bar is on the left side of the screen. private boolean mIsSeascape; // Notification dots @@ -587,28 +588,40 @@ public class DeviceProfile { /** * Gets an item's location on the home screen. This is useful if the home screen * is animating, otherwise use {@link View#getLocationOnScreen(int[])}. - * - * TODO(b/123900446): Handle landscape mode * @param pageDiff The page difference relative to the current page. */ public void getItemLocation(int cellX, int cellY, int spanX, int spanY, int container, int pageDiff, Rect outBounds) { outBounds.setEmpty(); - outBounds.left = mInsets.left - + workspacePadding.left + cellLayoutPaddingLeftRightPx + (cellX * getCellSize().x); - outBounds.top = mInsets.top; if (container == CONTAINER_HOTSEAT) { - outBounds.top += workspacePadding.top - + (inv.numRows * getCellSize().y) - + verticalDragHandleSizePx - - verticalDragHandleOverlapWorkspace; - outBounds.bottom = outBounds.top + hotseatBarSizePx - hotseatBarBottomPaddingPx; + final int actualHotseatCellHeight; + if (isVerticalBarLayout()) { + actualHotseatCellHeight = availableHeightPx / inv.numRows; + if (mIsSeascape) { + outBounds.left = mHotseatPadding.left; + } else { + outBounds.left = availableWidthPx - hotseatBarSizePx + mHotseatPadding.left; + } + outBounds.right = outBounds.left + iconSizePx; + outBounds.top = mHotseatPadding.top + + actualHotseatCellHeight * (inv.numRows - cellX - 1); + outBounds.bottom = outBounds.top + actualHotseatCellHeight; + } else { + actualHotseatCellHeight = hotseatBarSizePx - hotseatBarBottomPaddingPx + - hotseatBarTopPaddingPx; + outBounds.left = mInsets.left + workspacePadding.left + cellLayoutPaddingLeftRightPx + + (cellX * getCellSize().x); + outBounds.right = outBounds.left + getCellSize().x; + outBounds.top = mInsets.top + availableHeightPx - hotseatBarSizePx; + outBounds.bottom = outBounds.top + actualHotseatCellHeight; + } } else { - outBounds.top += workspacePadding.top + (cellY * getCellSize().y); + outBounds.left = mInsets.left + workspacePadding.left + cellLayoutPaddingLeftRightPx + + (cellX * getCellSize().x) + (pageDiff * availableWidthPx); + outBounds.right = outBounds.left + (getCellSize().x * spanX); + outBounds.top = mInsets.top + workspacePadding.top + (cellY * getCellSize().y); outBounds.bottom = outBounds.top + (getCellSize().y * spanY); - outBounds.left += (pageDiff) * availableWidthPx; } - outBounds.right = outBounds.left + (getCellSize().x * spanX); } public float getAspectRatioWithInsets() { From 7d7979df1e436d8815da7b007eba2897d8e34f9e Mon Sep 17 00:00:00 2001 From: vadimt Date: Thu, 7 Feb 2019 16:04:47 -0800 Subject: [PATCH 14/68] Not using reflection for app-usage API Bug: 118319143 Test: Manual Change-Id: I304c2d92f2cddccb6f187436cfd5465d06a14867 --- .../views/DigitalWellBeingToast.java | 46 ++++++------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java index 0aa1beb60d..19e9cb4ff6 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java @@ -23,6 +23,7 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; +import android.content.pm.LauncherApps.AppUsageLimit; import android.content.res.Resources; import android.icu.text.MeasureFormat; import android.icu.text.MeasureFormat.FormatWidth; @@ -44,13 +45,13 @@ import com.android.launcher3.Utilities; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.systemui.shared.recents.model.Task; -import java.lang.reflect.Method; import java.time.Duration; import java.util.Locale; public final class DigitalWellBeingToast extends LinearLayout { static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS); static final int MINUTE_MS = 60000; + private final LauncherApps mLauncherApps; public interface InitializeCallback { void call(float saturation, String contentDescription); @@ -67,6 +68,7 @@ public final class DigitalWellBeingToast extends LinearLayout { setLayoutDirection(Utilities.isRtl(getResources()) ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR); setOnClickListener((view) -> openAppUsageSettings()); + mLauncherApps = context.getSystemService(LauncherApps.class); } @Override @@ -87,47 +89,29 @@ public final class DigitalWellBeingToast extends LinearLayout { } Utilities.THREAD_POOL_EXECUTOR.execute(() -> { - long appUsageLimitTimeMs = -1; - long appRemainingTimeMs = -1; + final AppUsageLimit usageLimit = mLauncherApps.getAppUsageLimit( + task.getTopComponent().getPackageName(), + UserHandle.of(task.key.userId)); - try { - final Method getAppUsageLimit = LauncherApps.class.getMethod( - "getAppUsageLimit", - String.class, - UserHandle.class); - final Object usageLimit = getAppUsageLimit.invoke( - getContext().getSystemService(LauncherApps.class), - task.getTopComponent().getPackageName(), - UserHandle.of(task.key.userId)); - - if (usageLimit != null) { - final Class appUsageLimitClass = usageLimit.getClass(); - appUsageLimitTimeMs = (long) appUsageLimitClass.getMethod("getTotalUsageLimit"). - invoke(usageLimit); - appRemainingTimeMs = (long) appUsageLimitClass.getMethod("getUsageRemaining"). - invoke(usageLimit); - } - } catch (Exception e) { - // Do nothing - } - - final long appUsageLimitTimeMsFinal = appUsageLimitTimeMs; - final long appRemainingTimeMsFinal = appRemainingTimeMs; + final long appUsageLimitTimeMs = + usageLimit != null ? usageLimit.getTotalUsageLimit() : -1; + final long appRemainingTimeMs = + usageLimit != null ? usageLimit.getUsageRemaining() : -1; post(() -> { - if (appUsageLimitTimeMsFinal < 0) { + if (appUsageLimitTimeMs < 0) { setVisibility(GONE); } else { setVisibility(VISIBLE); - mText.setText(getText(appRemainingTimeMsFinal)); - mImage.setImageResource(appRemainingTimeMsFinal > 0 ? + mText.setText(getText(appRemainingTimeMs)); + mImage.setImageResource(appRemainingTimeMs > 0 ? R.drawable.hourglass_top : R.drawable.hourglass_bottom); } callback.call( - appUsageLimitTimeMsFinal >= 0 && appRemainingTimeMsFinal <= 0 ? 0 : 1, + appUsageLimitTimeMs >= 0 && appRemainingTimeMs <= 0 ? 0 : 1, getContentDescriptionForTask( - task, appUsageLimitTimeMsFinal, appRemainingTimeMsFinal)); + task, appUsageLimitTimeMs, appRemainingTimeMs)); }); }); } From c1f88db00a219bc617f63b59af2bb6d01d632d08 Mon Sep 17 00:00:00 2001 From: vadimt Date: Mon, 11 Mar 2019 13:37:54 -0700 Subject: [PATCH 15/68] Stress test for starting Launcher via gestures Ideally, we should have a race condition tests, but with the new single-threaded model, this requires more work. This is a temporary measure. Change-Id: Ie180883f4b186e4c4087dc77fed1e4d946402465 --- .../StartLauncherViaGestureTests.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java index fe789aadef..6031dcd711 100644 --- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java +++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java @@ -37,6 +37,9 @@ import org.junit.runner.RunWith; @LargeTest @RunWith(AndroidJUnit4.class) public class StartLauncherViaGestureTests extends AbstractQuickStepTest { + + static final int STRESS_REPEAT_COUNT = 10; + @Override @Before public void setUp() throws Exception { @@ -79,4 +82,28 @@ public class StartLauncherViaGestureTests extends AbstractQuickStepTest { closeLauncherActivity(); mLauncher.getBackground().switchToOverview(); } + + @Test + @QuickstepOnOff + public void testStressPressHome() { + for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) { + // Destroy Launcher activity. + closeLauncherActivity(); + + // The test action. + mLauncher.pressHome(); + } + } + + @Test + @QuickstepOnOff + public void testStressSwipeToOverview() { + for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) { + // Destroy Launcher activity. + closeLauncherActivity(); + + // The test action. + mLauncher.getBackground().switchToOverview(); + } + } } \ No newline at end of file From 93e0594b3dc27cccc2251af40ecc2f3ab60c1eec Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Mon, 11 Mar 2019 14:05:41 -0700 Subject: [PATCH 16/68] Fix crash when trying to load SystemShortcut drawable. Bug: 127807969 Change-Id: I9d7e8bf5b6b45fe6ad61984ab2c0326926076d75 --- src/com/android/launcher3/views/FloatingIconView.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index ec0462b886..5163e5f79d 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -34,6 +34,7 @@ import android.os.Looper; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; +import android.widget.ImageView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.InsettableFrameLayout.LayoutParams; @@ -48,6 +49,7 @@ import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.folder.FolderShape; import com.android.launcher3.graphics.ShiftedBitmapDrawable; import com.android.launcher3.icons.LauncherIcons; +import com.android.launcher3.popup.SystemShortcut; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; @@ -196,6 +198,9 @@ public class FloatingIconView extends View implements Animator.AnimatorListener, // Similar to DragView, we simply use the BubbleTextView icon here. mDrawable = ((BubbleTextView) v).getIcon(); } + if (v instanceof ImageView && info instanceof SystemShortcut) { + mDrawable = ((ImageView) v).getDrawable(); + } if (mDrawable == null) { mDrawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height, useDrawableAsIs, new Object[1]); From 1ae97d81f6fd77c76906d5b5019ef46c92786399 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 8 Mar 2019 17:10:51 -0800 Subject: [PATCH 17/68] Add TaskInputController for Recents Go Add TaskInputController, a class handling input logic for the Recents Go view. This CL hooks up the tap to launch logic with swiping + clear all to be added in a later CL. Bug: 114136250 Test: Build Launcher3IconRecentsGo and tap apps; see that they launch task Change-Id: I03e14a6f66307b9c6239cdeaec95d09f160fa3e0 --- .../com/android/quickstep/TaskAdapter.java | 10 ++++- .../src/com/android/quickstep/TaskHolder.java | 12 +++++ .../quickstep/TaskInputController.java | 45 +++++++++++++++++++ .../quickstep/views/IconRecentsView.java | 4 ++ 4 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 go/quickstep/src/com/android/quickstep/TaskInputController.java diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java index d3fd24026c..99446d07df 100644 --- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java +++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java @@ -35,17 +35,23 @@ public final class TaskAdapter extends Adapter { private static final int MAX_TASKS_TO_DISPLAY = 6; private static final String TAG = "TaskAdapter"; private final TaskListLoader mLoader; + private TaskInputController mInputController; public TaskAdapter(@NonNull TaskListLoader loader) { mLoader = loader; } + public void setInputController(TaskInputController inputController) { + mInputController = inputController; + } + @Override public TaskHolder onCreateViewHolder(ViewGroup parent, int viewType) { - // TODO: Swap in an actual task view here (view w/ icon, label, etc.) TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext()) .inflate(R.layout.task_item_view, parent, false); - return new TaskHolder(itemView); + TaskHolder holder = new TaskHolder(itemView); + itemView.setOnClickListener(view -> mInputController.onTaskClicked(holder)); + return holder; } @Override diff --git a/go/quickstep/src/com/android/quickstep/TaskHolder.java b/go/quickstep/src/com/android/quickstep/TaskHolder.java index 47398c876e..67e8ece227 100644 --- a/go/quickstep/src/com/android/quickstep/TaskHolder.java +++ b/go/quickstep/src/com/android/quickstep/TaskHolder.java @@ -15,6 +15,7 @@ */ package com.android.quickstep; +import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView.ViewHolder; import com.android.quickstep.views.TaskItemView; @@ -27,6 +28,7 @@ import com.android.systemui.shared.recents.model.Task; final class TaskHolder extends ViewHolder { private final TaskItemView mTaskItemView; + private Task mTask; public TaskHolder(TaskItemView itemView) { super(itemView); @@ -40,7 +42,17 @@ final class TaskHolder extends ViewHolder { * @param task the task to bind to the view */ public void bindTask(Task task) { + mTask = task; mTaskItemView.setLabel(task.titleDescription); mTaskItemView.setIcon(task.icon); } + + /** + * Gets the task currently bound to this view + * + * @return the current task + */ + public @NonNull Task getTask() { + return mTask; + } } diff --git a/go/quickstep/src/com/android/quickstep/TaskInputController.java b/go/quickstep/src/com/android/quickstep/TaskInputController.java new file mode 100644 index 0000000000..66c4496494 --- /dev/null +++ b/go/quickstep/src/com/android/quickstep/TaskInputController.java @@ -0,0 +1,45 @@ +/* + * 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 com.android.systemui.shared.system.ActivityManagerWrapper; + +/** + * Controller responsible for task logic that occurs on various input to the recents view. + */ +public final class TaskInputController { + + TaskAdapter mAdapter; + + public TaskInputController(TaskAdapter adapter) { + mAdapter = adapter; + } + + /** + * Logic that occurs when a task view is tapped. Launches the respective task. + * + * @param viewHolder the task view holder that has been tapped + */ + public void onTaskClicked(TaskHolder viewHolder) { + // TODO: Add app launch animation as part of the launch options here. + ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(viewHolder.getTask().key, + null /* options */, null /* resultCallback */, null /* resultCallbackHandler */); + } + + // TODO: Implement swipe to delete and notify adapter that data has updated + + // TODO: Implement "Clear all" and notify adapter that data has updated +} diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index afb05409ca..e8a915f640 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -28,6 +28,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.R; import com.android.quickstep.TaskAdapter; +import com.android.quickstep.TaskInputController; import com.android.quickstep.TaskListLoader; /** @@ -79,6 +80,7 @@ public final class IconRecentsView extends FrameLayout { private float mTranslationYFactor; private TaskAdapter mTaskAdapter; private RecyclerView mTaskRecyclerView; + private TaskInputController mTaskInputController; private TaskListLoader mTaskLoader; public IconRecentsView(Context context, AttributeSet attrs) { @@ -91,6 +93,8 @@ public final class IconRecentsView extends FrameLayout { super.onFinishInflate(); mTaskLoader = new TaskListLoader(mContext); mTaskAdapter = new TaskAdapter(mTaskLoader); + mTaskInputController = new TaskInputController(mTaskAdapter); + mTaskAdapter.setInputController(mTaskInputController); mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view); mTaskRecyclerView.setAdapter(mTaskAdapter); mTaskRecyclerView.setLayoutManager( From a014ffa3ecdbaf989a850d36c1cd431ee215eb1e Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Mon, 11 Mar 2019 15:25:49 -0700 Subject: [PATCH 18/68] Set window corner radius to 0 for app tranition anims in multi-window mode. Otherwise we see a jump from corner radius to 0 corner radius when the animation completes. Change-Id: I46a65342d4ea531dc16c866aee305478067959d3 --- .../launcher3/QuickstepAppTransitionManagerImpl.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java index f8b167b48c..c5475d6874 100644 --- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java +++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java @@ -559,7 +559,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans float transY0 = floatingViewBounds[1] - offsetY; float windowRadius = 0; - if (RecentsModel.INSTANCE.get(mLauncher).supportsRoundedCornersOnWindows()) { + if (!mDeviceProfile.isMultiWindowMode && + RecentsModel.INSTANCE.get(mLauncher).supportsRoundedCornersOnWindows()) { windowRadius = RecentsModel.INSTANCE.get(mLauncher) .getWindowCornerRadius(); } @@ -700,7 +701,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans new SyncRtSurfaceTransactionApplierCompat(mDragLayer); ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1); unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS); - float cornerRadius = RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius(); + float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 : + RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius(); unlockAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { @@ -726,7 +728,8 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans Matrix matrix = new Matrix(); ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1); int duration = CLOSING_TRANSITION_DURATION_MS; - float windowCornerRadius = RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius(); + float windowCornerRadius = mDeviceProfile.isMultiWindowMode ? 0 : + RecentsModel.INSTANCE.get(mLauncher).getWindowCornerRadius(); closingAnimator.setDuration(duration); closingAnimator.addUpdateListener(new MultiValueUpdateListener() { FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7); From 270d29c43e65a61e4a1f93714ef775ff2a4c2198 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Tue, 12 Mar 2019 14:28:45 -0700 Subject: [PATCH 19/68] Only get drawable on the background thread, everything else on main thread. Bug: 128259687 Change-Id: I6230bfdc292e9a43ab935cfb6debb7a6fac4d4f7 --- .../launcher3/views/FloatingIconView.java | 124 +++++++++--------- 1 file changed, 65 insertions(+), 59 deletions(-) diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index 5163e5f79d..7af2bd87b0 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -67,7 +67,6 @@ public class FloatingIconView extends View implements Animator.AnimatorListener, private Runnable mStartRunnable; private Runnable mEndRunnable; - private Drawable mDrawable; private int mOriginalHeight; private final int mBlurSizeOutline; @@ -191,73 +190,79 @@ public class FloatingIconView extends View implements Animator.AnimatorListener, private void getIcon(Launcher launcher, View v, ItemInfo info, boolean useDrawableAsIs, float aspectRatio) { final LayoutParams lp = (LayoutParams) getLayoutParams(); - + Drawable drawable = null; boolean supportsAdaptiveIcons = ADAPTIVE_ICON_WINDOW_ANIM.get() && !useDrawableAsIs && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; if (!supportsAdaptiveIcons && v instanceof BubbleTextView) { // Similar to DragView, we simply use the BubbleTextView icon here. - mDrawable = ((BubbleTextView) v).getIcon(); + drawable = ((BubbleTextView) v).getIcon(); } if (v instanceof ImageView && info instanceof SystemShortcut) { - mDrawable = ((ImageView) v).getDrawable(); + drawable = ((ImageView) v).getDrawable(); } - if (mDrawable == null) { - mDrawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height, + if (drawable == null) { + drawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height, useDrawableAsIs, new Object[1]); } - if (supportsAdaptiveIcons && mDrawable instanceof AdaptiveIconDrawable) { - mIsAdaptiveIcon = true; - - AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) mDrawable; - Drawable background = adaptiveIcon.getBackground(); - if (background == null) { - background = new ColorDrawable(Color.TRANSPARENT); - } - mBackground = background; - Drawable foreground = adaptiveIcon.getForeground(); - if (foreground == null) { - foreground = new ColorDrawable(Color.TRANSPARENT); - } - mForeground = foreground; - - int offset = getOffsetForAdaptiveIconBounds(); - mFinalDrawableBounds.set(offset, offset, lp.width - offset, mOriginalHeight - offset); - if (mForeground instanceof ShiftedBitmapDrawable && v instanceof FolderIcon) { - ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mForeground; - ((FolderIcon) v).getPreviewBounds(sTmpRect); - sbd.setShiftX(sbd.getShiftX() - sTmpRect.left); - sbd.setShiftY(sbd.getShiftY() - sTmpRect.top); - } - mForeground.setBounds(mFinalDrawableBounds); - mBackground.setBounds(mFinalDrawableBounds); - - int blurMargin = mBlurSizeOutline / 2; - mStartRevealRect.set(blurMargin, blurMargin , lp.width - blurMargin, - mOriginalHeight - blurMargin); - - if (aspectRatio > 0) { - lp.height = (int) Math.max(lp.height, lp.width * aspectRatio); - layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin - + lp.height); - } - mBgDrawableStartScale = (float) lp.height / mOriginalHeight; - setBackgroundDrawableBounds(mBgDrawableStartScale); - - // Set up outline - mOutline.set(0, 0, lp.width, lp.height); - setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setRoundRect(mOutline, mTaskCornerRadius); - } - }); - setClipToOutline(true); - } else { - setBackground(mDrawable); - } + Drawable finalDrawable = drawable == null ? null + : drawable.getConstantState().newDrawable(); + boolean isAdaptiveIcon = supportsAdaptiveIcons + && finalDrawable instanceof AdaptiveIconDrawable; + int iconOffset = getOffsetForIconBounds(finalDrawable); new Handler(Looper.getMainLooper()).post(() -> { + if (isAdaptiveIcon) { + mIsAdaptiveIcon = true; + + AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) finalDrawable; + Drawable background = adaptiveIcon.getBackground(); + if (background == null) { + background = new ColorDrawable(Color.TRANSPARENT); + } + mBackground = background; + Drawable foreground = adaptiveIcon.getForeground(); + if (foreground == null) { + foreground = new ColorDrawable(Color.TRANSPARENT); + } + mForeground = foreground; + + mFinalDrawableBounds.set(iconOffset, iconOffset, lp.width - + iconOffset, mOriginalHeight - iconOffset); + if (mForeground instanceof ShiftedBitmapDrawable && v instanceof FolderIcon) { + ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mForeground; + ((FolderIcon) v).getPreviewBounds(sTmpRect); + sbd.setShiftX(sbd.getShiftX() - sTmpRect.left); + sbd.setShiftY(sbd.getShiftY() - sTmpRect.top); + } + mForeground.setBounds(mFinalDrawableBounds); + mBackground.setBounds(mFinalDrawableBounds); + + int blurMargin = mBlurSizeOutline / 2; + mStartRevealRect.set(blurMargin, blurMargin , lp.width - blurMargin, + mOriginalHeight - blurMargin); + + if (aspectRatio > 0) { + lp.height = (int) Math.max(lp.height, lp.width * aspectRatio); + layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin + + lp.height); + } + mBgDrawableStartScale = (float) lp.height / mOriginalHeight; + setBackgroundDrawableBounds(mBgDrawableStartScale); + + // Set up outline + mOutline.set(0, 0, lp.width, lp.height); + setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(mOutline, mTaskCornerRadius); + } + }); + setClipToOutline(true); + } else { + setBackground(finalDrawable); + } + invalidate(); invalidateOutline(); }); @@ -272,9 +277,10 @@ public class FloatingIconView extends View implements Animator.AnimatorListener, mBackground.setBounds(mBgDrawableBounds); } - private int getOffsetForAdaptiveIconBounds() { + @WorkerThread + private int getOffsetForIconBounds(Drawable drawable) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O || - !(mDrawable instanceof AdaptiveIconDrawable)) { + !(drawable instanceof AdaptiveIconDrawable)) { return 0; } @@ -283,7 +289,7 @@ public class FloatingIconView extends View implements Animator.AnimatorListener, bounds.inset(mBlurSizeOutline / 2, mBlurSizeOutline / 2); try (LauncherIcons li = LauncherIcons.obtain(Launcher.fromContext(getContext()))) { - Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(mDrawable, null)); + Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(drawable, null)); } bounds.inset( From 013af455b1b4aec350f80322f817a72151145d92 Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 12 Mar 2019 11:59:44 -0700 Subject: [PATCH 20/68] Implement swipe to delete on Recents Go Introduce a ItemTouchHelper.Callback implementation that hooks up remove logic to swiping right on task items. Additionally, tighten the modification access to the task list in TaskListLoader so it is harder to mis-use. Bug: 114136250 Test: Removed app via swiping Change-Id: I9b6e2783d1f6d56e655e9b7ffa9af85328f98159 --- .../com/android/quickstep/TaskAdapter.java | 5 +- .../quickstep/TaskInputController.java | 16 +++++-- .../com/android/quickstep/TaskListLoader.java | 16 +++++-- .../android/quickstep/TaskSwipeCallback.java | 48 +++++++++++++++++++ .../quickstep/views/IconRecentsView.java | 30 +++++++----- 5 files changed, 95 insertions(+), 20 deletions(-) create mode 100644 go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java index 99446d07df..4f3d1e4a03 100644 --- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java +++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java @@ -25,7 +25,8 @@ import com.android.launcher3.R; import com.android.quickstep.views.TaskItemView; import com.android.systemui.shared.recents.model.Task; -import java.util.ArrayList; +import java.util.List; + /** * Recycler view adapter that dynamically inflates and binds {@link TaskHolder} instances with the * appropriate {@link Task} from the recents task list. @@ -56,7 +57,7 @@ public final class TaskAdapter extends Adapter { @Override public void onBindViewHolder(TaskHolder holder, int position) { - ArrayList tasks = mLoader.getCurrentTaskList(); + List tasks = mLoader.getCurrentTaskList(); if (position >= tasks.size()) { // Task list has updated. return; diff --git a/go/quickstep/src/com/android/quickstep/TaskInputController.java b/go/quickstep/src/com/android/quickstep/TaskInputController.java index 66c4496494..d97ac8da90 100644 --- a/go/quickstep/src/com/android/quickstep/TaskInputController.java +++ b/go/quickstep/src/com/android/quickstep/TaskInputController.java @@ -15,6 +15,7 @@ */ package com.android.quickstep; +import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.ActivityManagerWrapper; /** @@ -22,9 +23,11 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; */ public final class TaskInputController { - TaskAdapter mAdapter; + private final TaskListLoader mLoader; + private final TaskAdapter mAdapter; - public TaskInputController(TaskAdapter adapter) { + public TaskInputController(TaskListLoader loader,TaskAdapter adapter) { + mLoader = loader; mAdapter = adapter; } @@ -39,7 +42,14 @@ public final class TaskInputController { null /* options */, null /* resultCallback */, null /* resultCallbackHandler */); } - // TODO: Implement swipe to delete and notify adapter that data has updated + public void onTaskSwiped(TaskHolder viewHolder) { + int position = viewHolder.getAdapterPosition(); + Task task = viewHolder.getTask(); + ActivityManagerWrapper.getInstance().removeTask(task.key.id); + mLoader.removeTask(task); + mAdapter.notifyItemRemoved(position); + } // TODO: Implement "Clear all" and notify adapter that data has updated + } diff --git a/go/quickstep/src/com/android/quickstep/TaskListLoader.java b/go/quickstep/src/com/android/quickstep/TaskListLoader.java index c798cef1ee..e6d1a22df3 100644 --- a/go/quickstep/src/com/android/quickstep/TaskListLoader.java +++ b/go/quickstep/src/com/android/quickstep/TaskListLoader.java @@ -24,6 +24,7 @@ import com.android.systemui.shared.recents.model.Task; import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -45,13 +46,13 @@ public final class TaskListLoader { /** * Returns the current task list as of the last completed load (see - * {@link #loadTaskList}). This list of tasks is guaranteed to always have all its task - * content loaded. + * {@link #loadTaskList}) as a read-only list. This list of tasks is guaranteed to always have + * all its task content loaded. * * @return the current list of tasks w/ all content loaded */ - public ArrayList getCurrentTaskList() { - return mTaskList; + public List getCurrentTaskList() { + return Collections.unmodifiableList(mTaskList); } /** @@ -84,6 +85,13 @@ public final class TaskListLoader { }); } + /** + * Removes the task from the current task list. + */ + void removeTask(Task task) { + mTaskList.remove(task); + } + /** * Loads task content for a list of tasks, including the label, icon, and thumbnail. For content * that isn't cached, load the content asynchronously in the background. diff --git a/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java b/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java new file mode 100644 index 0000000000..2a539172b2 --- /dev/null +++ b/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java @@ -0,0 +1,48 @@ +/* + * 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 androidx.recyclerview.widget.ItemTouchHelper.RIGHT; + +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; + +/** + * Callback for swipe input on {@link TaskHolder} views in the recents view. + */ +public final class TaskSwipeCallback extends ItemTouchHelper.SimpleCallback { + + private final TaskInputController mTaskInputController; + + public TaskSwipeCallback(TaskInputController inputController) { + super(0 /* dragDirs */, RIGHT); + mTaskInputController = inputController; + } + + @Override + public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, + ViewHolder target) { + return false; + } + + @Override + public void onSwiped(ViewHolder viewHolder, int direction) { + if (direction == RIGHT) { + mTaskInputController.onTaskSwiped((TaskHolder) viewHolder); + } + } +} diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index e8a915f640..b7740eb50b 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -23,6 +23,7 @@ import android.util.FloatProperty; import android.view.ViewDebug; import android.widget.FrameLayout; +import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -30,6 +31,7 @@ import com.android.launcher3.R; import com.android.quickstep.TaskAdapter; import com.android.quickstep.TaskInputController; import com.android.quickstep.TaskListLoader; +import com.android.quickstep.TaskSwipeCallback; /** * Root view for the icon recents view. Acts as the main interface to the rest of the Launcher code @@ -76,29 +78,35 @@ public final class IconRecentsView extends FrameLayout { @ViewDebug.ExportedProperty(category = "launcher") private final Context mContext; + private final TaskListLoader mTaskLoader; + private final TaskAdapter mTaskAdapter; + private final TaskInputController mTaskInputController; private float mTranslationYFactor; - private TaskAdapter mTaskAdapter; private RecyclerView mTaskRecyclerView; - private TaskInputController mTaskInputController; - private TaskListLoader mTaskLoader; + public IconRecentsView(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; + mTaskLoader = new TaskListLoader(mContext); + mTaskAdapter = new TaskAdapter(mTaskLoader); + mTaskInputController = new TaskInputController(mTaskLoader, mTaskAdapter); + mTaskAdapter.setInputController(mTaskInputController); } @Override protected void onFinishInflate() { super.onFinishInflate(); - mTaskLoader = new TaskListLoader(mContext); - mTaskAdapter = new TaskAdapter(mTaskLoader); - mTaskInputController = new TaskInputController(mTaskAdapter); - mTaskAdapter.setInputController(mTaskInputController); - mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view); - mTaskRecyclerView.setAdapter(mTaskAdapter); - mTaskRecyclerView.setLayoutManager( - new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */)); + if (mTaskRecyclerView == null) { + mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view); + mTaskRecyclerView.setAdapter(mTaskAdapter); + mTaskRecyclerView.setLayoutManager( + new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */)); + ItemTouchHelper helper = new ItemTouchHelper( + new TaskSwipeCallback(mTaskInputController)); + helper.attachToRecyclerView(mTaskRecyclerView); + } } /** From 58ddac1d09a05b3fc8359545285d363407883f37 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 12 Mar 2019 16:22:57 -0700 Subject: [PATCH 21/68] Removing fade animation on swipe up Fade animation is no longer required as the transform is also handled on the main thread. So we will get the RecentsAnimaiton always after Launcher has drawn Change-Id: Ieb05406c9a5b9132b0518155bdbda16c1f73a6ec --- .../FallbackActivityControllerHelper.java | 6 -- .../LauncherActivityControllerHelper.java | 7 -- .../FallbackActivityControllerHelper.java | 5 -- .../LauncherActivityControllerHelper.java | 5 -- .../android/quickstep/MultiStateCallback.java | 5 ++ .../WindowTransformSwipeHandler.java | 71 ++++++------------- .../quickstep/ActivityControlHelper.java | 3 - .../launcher3/dragndrop/DragLayer.java | 3 +- 8 files changed, 26 insertions(+), 79 deletions(-) diff --git a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java index abb9242247..bba08a40af 100644 --- a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java +++ b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java @@ -26,7 +26,6 @@ import androidx.annotation.Nullable; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.userevent.nano.LauncherLogProto; -import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.quickstep.util.RemoteAnimationTargetSet; import com.android.quickstep.views.IconRecentsView; @@ -109,11 +108,6 @@ public final class FallbackActivityControllerHelper extends return false; } - @Override - public AlphaProperty getAlphaProperty(RecentsActivity activity) { - return activity.getDragLayer().getAlphaProperty(0); - } - @Override public int getContainerType() { return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER; diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java index 76685f3410..d55d2e5fd9 100644 --- a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -22,9 +22,7 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherInitListener; import com.android.launcher3.anim.AnimatorPlaybackController; -import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.userevent.nano.LauncherLogProto; -import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.quickstep.views.IconRecentsView; import java.util.function.BiPredicate; @@ -93,11 +91,6 @@ public final class LauncherActivityControllerHelper extends GoActivityControlHel return true; } - @Override - public AlphaProperty getAlphaProperty(Launcher activity) { - return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP); - } - @Override public int getContainerType() { final Launcher launcher = getVisibleLauncher(); 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 1ed1353c6c..d61ed724dd 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java @@ -181,11 +181,6 @@ public final class FallbackActivityControllerHelper implements return false; } - @Override - public AlphaProperty getAlphaProperty(RecentsActivity activity) { - return activity.getDragLayer().getAlphaProperty(0); - } - @Override public int getContainerType() { return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER; 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 eee03441db..e95e2a0de5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -393,11 +393,6 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe return true; } - @Override - public AlphaProperty getAlphaProperty(Launcher activity) { - return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP); - } - @Override public int getContainerType() { final Launcher launcher = getVisibleLauncher(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java index 9fceab4ac7..357c9fc35e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java @@ -18,6 +18,8 @@ package com.android.quickstep; import android.util.Log; import android.util.SparseArray; +import com.android.launcher3.config.FeatureFlags; + import java.util.StringJoiner; import java.util.function.Consumer; @@ -101,6 +103,9 @@ public class MultiStateCallback { * The callback is only run once. */ public void addCallback(int stateMask, Runnable callback) { + if (FeatureFlags.IS_DOGFOOD_BUILD && mCallbacks.get(stateMask) != null) { + throw new IllegalStateException("Multiple callbacks on same state"); + } mCallbacks.put(stateMask, callback); } 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 7bea5935c4..00dbdff1bf 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -40,7 +40,6 @@ import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD; import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; @@ -78,8 +77,6 @@ import com.android.launcher3.logging.UserEventDispatcher; 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.MultiValueAlpha; -import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.launcher3.util.RaceConditionTracker; import com.android.launcher3.util.TraceHelper; import com.android.launcher3.views.FloatingIconView; @@ -113,7 +110,7 @@ public class WindowTransformSwipeHandler implements SwipeAnimationListener, OnApplyWindowInsetsListener { private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName(); - private static final String[] STATE_NAMES = DEBUG_STATES ? new String[19] : null; + private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null; private static int getFlagForIndex(int index, String name) { if (DEBUG_STATES) { @@ -126,47 +123,44 @@ public class WindowTransformSwipeHandler private static final int STATE_LAUNCHER_PRESENT = getFlagForIndex(0, "STATE_LAUNCHER_PRESENT"); private static final int STATE_LAUNCHER_STARTED = getFlagForIndex(1, "STATE_LAUNCHER_STARTED"); private static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN"); - private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE = - getFlagForIndex(3, "STATE_ACTIVITY_MULTIPLIER_COMPLETE"); // Internal initialization states private static final int STATE_APP_CONTROLLER_RECEIVED = - getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED"); + getFlagForIndex(3, "STATE_APP_CONTROLLER_RECEIVED"); // Interaction finish states private static final int STATE_SCALED_CONTROLLER_HOME = - getFlagForIndex(5, "STATE_SCALED_CONTROLLER_HOME"); + getFlagForIndex(4, "STATE_SCALED_CONTROLLER_HOME"); private static final int STATE_SCALED_CONTROLLER_RECENTS = - getFlagForIndex(6, "STATE_SCALED_CONTROLLER_RECENTS"); + getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS"); private static final int STATE_SCALED_CONTROLLER_LAST_TASK = - getFlagForIndex(7, "STATE_SCALED_CONTROLLER_LAST_TASK"); + getFlagForIndex(6, "STATE_SCALED_CONTROLLER_LAST_TASK"); private static final int STATE_HANDLER_INVALIDATED = - getFlagForIndex(8, "STATE_HANDLER_INVALIDATED"); + getFlagForIndex(7, "STATE_HANDLER_INVALIDATED"); private static final int STATE_GESTURE_STARTED = - getFlagForIndex(9, "STATE_GESTURE_STARTED"); + getFlagForIndex(8, "STATE_GESTURE_STARTED"); private static final int STATE_GESTURE_CANCELLED = - getFlagForIndex(10, "STATE_GESTURE_CANCELLED"); + getFlagForIndex(9, "STATE_GESTURE_CANCELLED"); private static final int STATE_GESTURE_COMPLETED = - getFlagForIndex(11, "STATE_GESTURE_COMPLETED"); + getFlagForIndex(10, "STATE_GESTURE_COMPLETED"); private static final int STATE_CAPTURE_SCREENSHOT = - getFlagForIndex(12, "STATE_CAPTURE_SCREENSHOT"); + getFlagForIndex(11, "STATE_CAPTURE_SCREENSHOT"); private static final int STATE_SCREENSHOT_CAPTURED = - getFlagForIndex(13, "STATE_SCREENSHOT_CAPTURED"); + getFlagForIndex(12, "STATE_SCREENSHOT_CAPTURED"); private static final int STATE_SCREENSHOT_VIEW_SHOWN = - getFlagForIndex(14, "STATE_SCREENSHOT_VIEW_SHOWN"); + getFlagForIndex(13, "STATE_SCREENSHOT_VIEW_SHOWN"); private static final int STATE_RESUME_LAST_TASK = - getFlagForIndex(15, "STATE_RESUME_LAST_TASK"); + getFlagForIndex(14, "STATE_RESUME_LAST_TASK"); private static final int STATE_START_NEW_TASK = - getFlagForIndex(16, "STATE_START_NEW_TASK"); + getFlagForIndex(15, "STATE_START_NEW_TASK"); private static final int STATE_CURRENT_TASK_FINISHED = - getFlagForIndex(17, "STATE_CURRENT_TASK_FINISHED"); + getFlagForIndex(16, "STATE_CURRENT_TASK_FINISHED"); private static final int LAUNCHER_UI_STATES = - STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_ACTIVITY_MULTIPLIER_COMPLETE - | STATE_LAUNCHER_STARTED; + STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED; // For debugging, keep in sync with above states @@ -226,7 +220,6 @@ public class WindowTransformSwipeHandler // 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 mDispatchedDownEvent; private boolean mContinuingLastGesture; // To avoid UI jump when gesture is started, we offset the animation by the threshold. private float mShiftAtGestureStart = 0; @@ -238,7 +231,6 @@ public class WindowTransformSwipeHandler private final ActivityInitListener mActivityInitListener; private final int mRunningTaskId; - private final RunningTaskInfo mRunningTaskInfo; private ThumbnailData mTaskSnapshot; private MultiStateCallback mStateCallback; @@ -265,7 +257,6 @@ public class WindowTransformSwipeHandler long touchTimeMs, ActivityControlHelper controller, boolean continuingLastGesture, InputConsumerController inputConsumer) { mContext = context; - mRunningTaskInfo = runningTaskInfo; mRunningTaskId = runningTaskInfo.id; mTouchTimeMs = touchTimeMs; mActivityControlHelper = controller; @@ -312,8 +303,7 @@ public class WindowTransformSwipeHandler this::startNewTask); mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED - | STATE_ACTIVITY_MULTIPLIER_COMPLETE - | STATE_CAPTURE_SCREENSHOT, + | STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT, this::switchToScreenshot); mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED @@ -322,13 +312,13 @@ public class WindowTransformSwipeHandler mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_COMPLETED | STATE_SCALED_CONTROLLER_HOME | STATE_APP_CONTROLLER_RECEIVED - | STATE_ACTIVITY_MULTIPLIER_COMPLETE, + | STATE_LAUNCHER_DRAWN, this::finishCurrentTransitionToHome); mStateCallback.addCallback(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED, this::reset); mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED - | STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_SCALED_CONTROLLER_RECENTS + | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED | STATE_GESTURE_STARTED, this::setupLauncherUiAfterSwipeUpAnimation); @@ -440,11 +430,10 @@ public class WindowTransformSwipeHandler AbstractFloatingView.closeAllOpenViews(activity, mWasLauncherAlreadyVisible); if (mWasLauncherAlreadyVisible) { - mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_LAUNCHER_DRAWN); + mStateCallback.setState(STATE_LAUNCHER_DRAWN); } else { TraceHelper.beginSection("WTS-init"); View dragLayer = activity.getDragLayer(); - mActivityControlHelper.getAlphaProperty(activity).setValue(0); dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() { @Override @@ -477,25 +466,6 @@ public class WindowTransformSwipeHandler } private void launcherFrameDrawn() { - AlphaProperty property = mActivityControlHelper.getAlphaProperty(mActivity); - if (property.getValue() < 1) { - if (mGestureStarted) { - final MultiStateCallback callback = mStateCallback; - ObjectAnimator animator = ObjectAnimator.ofFloat( - property, MultiValueAlpha.VALUE, 1); - animator.setDuration(getFadeInDuration()).addListener( - new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - callback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE); - } - }); - animator.start(); - } else { - property.setValue(1); - mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE); - } - } mLauncherFrameDrawnTime = SystemClock.uptimeMillis(); } @@ -1074,7 +1044,6 @@ public class WindowTransformSwipeHandler private void invalidateHandlerWithLauncher() { mLauncherTransitionController = null; - mActivityControlHelper.getAlphaProperty(mActivity).setValue(1); mRecentsView.setEnableFreeScroll(true); mRecentsView.setRunningTaskIconScaledDown(false); diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java index eccef04286..75be2e48e1 100644 --- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java +++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java @@ -31,7 +31,6 @@ import android.view.animation.Interpolator; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.anim.AnimatorPlaybackController; -import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.quickstep.util.RemoteAnimationProvider; import com.android.quickstep.util.RemoteAnimationTargetSet; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -85,8 +84,6 @@ public interface ActivityControlHelper { return true; } - AlphaProperty getAlphaProperty(T activity); - /** * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher} */ diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index f005ce7bf7..6950a1fad6 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -63,8 +63,7 @@ public class DragLayer extends BaseDragLayer { public static final int ALPHA_INDEX_OVERLAY = 0; public static final int ALPHA_INDEX_LAUNCHER_LOAD = 1; public static final int ALPHA_INDEX_TRANSITIONS = 2; - public static final int ALPHA_INDEX_SWIPE_UP = 3; - private static final int ALPHA_CHANNEL_COUNT = 4; + private static final int ALPHA_CHANNEL_COUNT = 3; public static final int ANIMATION_END_DISAPPEAR = 0; public static final int ANIMATION_END_REMAIN_VISIBLE = 2; From e8edaff41a61478f7e10ce507d27fe657651b6cf Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Tue, 12 Mar 2019 11:37:10 -0700 Subject: [PATCH 22/68] Log back key events from Overview in live tile mode. Change-Id: If633df84fc46f8b2c80785f983ac5b77a5553747 Fixes: 127837913 Test: N/A --- .../src/com/android/launcher3/uioverrides/OverviewState.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java index 3602508a5e..f4ea9f9bfa 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java @@ -18,6 +18,7 @@ package com.android.launcher3.uioverrides; import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS; import static com.android.launcher3.anim.Interpolators.DEACCEL_2; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; +import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE; import android.graphics.Rect; @@ -30,8 +31,8 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.Workspace; import com.android.launcher3.allapps.DiscoveryBounce; +import com.android.launcher3.userevent.nano.LauncherLogProto.Action; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; -import com.android.quickstep.RecentsModel; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; @@ -139,6 +140,8 @@ public class OverviewState extends LauncherState { public void onBackPressed(Launcher launcher) { TaskView taskView = launcher.getOverviewPanel().getRunningTaskView(); if (taskView != null) { + launcher.getUserEventDispatcher().logActionCommand(Action.Command.BACK, + newContainerTarget(ContainerType.OVERVIEW)); taskView.launchTask(true); } else { super.onBackPressed(launcher); From 5bc0dd07088ee80bbb65190ae5956d4168d06b24 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Tue, 12 Mar 2019 17:19:30 -0700 Subject: [PATCH 23/68] Fix bugs related to swipe up to home animation. - Added ConstantState support for FolderAdaptiveIcon and ShiftedBitmapDrawable. - Quick fix for NPE in Workspace#mapOverCellLayout while I investigate further. Bug: 128460496 Change-Id: I5ec02e25dcf9f17aeb37928e675a033bdc8819ae --- src/com/android/launcher3/Workspace.java | 4 ++ .../dragndrop/FolderAdaptiveIcon.java | 38 +++++++++++++++++-- .../graphics/ShiftedBitmapDrawable.java | 31 +++++++++++++++ 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 09eb6d9975..9427675851 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -3084,6 +3084,10 @@ public class Workspace extends PagedView } private boolean mapOverCellLayout(boolean recurse, CellLayout layout, ItemOperator op) { + // TODO(b/128460496) Potential race condition where layout is not yet loaded + if (layout == null) { + return false; + } ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets(); // map over all the shortcuts on the workspace final int itemCount = container.getChildCount(); diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java index 5e41bb70e4..0c5a1fc5a8 100644 --- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java +++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java @@ -19,11 +19,8 @@ package com.android.launcher3.dragndrop; import android.annotation.TargetApi; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.ColorFilter; import android.graphics.Matrix; -import android.graphics.Paint; import android.graphics.Path; -import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.ColorDrawable; @@ -49,11 +46,15 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable { private final Drawable mBadge; private final Path mMask; + private final ConstantState mConstantState; private FolderAdaptiveIcon(Drawable bg, Drawable fg, Drawable badge, Path mask) { super(bg, fg); mBadge = badge; mMask = mask; + + mConstantState = new MyConstantState(bg.getConstantState(), fg.getConstantState(), + badge.getConstantState(), mask); } @Override @@ -134,4 +135,35 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable { return new FolderAdaptiveIcon(new ColorDrawable(bg.getBgColor()), foreground, badge, mask); } + + @Override + public ConstantState getConstantState() { + return mConstantState; + } + + private static class MyConstantState extends ConstantState { + private final ConstantState mBg; + private final ConstantState mFg; + private final ConstantState mBadge; + private final Path mMask; + + MyConstantState(ConstantState bg, ConstantState fg, ConstantState badge, Path mask) { + mBg = bg; + mFg = fg; + mBadge = badge; + mMask = mask; + } + + @Override + public Drawable newDrawable() { + return new FolderAdaptiveIcon(mBg.newDrawable(), mFg.newDrawable(), + mBadge.newDrawable(), mMask); + } + + @Override + public int getChangingConfigurations() { + return mBg.getChangingConfigurations() & mFg.getChangingConfigurations() + & mBadge.getChangingConfigurations(); + } + } } diff --git a/src/com/android/launcher3/graphics/ShiftedBitmapDrawable.java b/src/com/android/launcher3/graphics/ShiftedBitmapDrawable.java index 52d45bb4e9..f8583b89e0 100644 --- a/src/com/android/launcher3/graphics/ShiftedBitmapDrawable.java +++ b/src/com/android/launcher3/graphics/ShiftedBitmapDrawable.java @@ -32,10 +32,14 @@ public class ShiftedBitmapDrawable extends Drawable { private float mShiftX; private float mShiftY; + private final ConstantState mConstantState; + public ShiftedBitmapDrawable(Bitmap bitmap, float shiftX, float shiftY) { mBitmap = bitmap; mShiftX = shiftX; mShiftY = shiftY; + + mConstantState = new MyConstantState(mBitmap, mShiftX, mShiftY); } public float getShiftX() { @@ -71,4 +75,31 @@ public class ShiftedBitmapDrawable extends Drawable { public int getOpacity() { return PixelFormat.TRANSLUCENT; } + + @Override + public ConstantState getConstantState() { + return mConstantState; + } + + private static class MyConstantState extends ConstantState { + private final Bitmap mBitmap; + private float mShiftX; + private float mShiftY; + + MyConstantState(Bitmap bitmap, float shiftX, float shiftY) { + mBitmap = bitmap; + mShiftX = shiftX; + mShiftY = shiftY; + } + + @Override + public Drawable newDrawable() { + return new ShiftedBitmapDrawable(mBitmap, mShiftX, mShiftY); + } + + @Override + public int getChangingConfigurations() { + return 0; + } + } } \ No newline at end of file From 6f064f9d3f8f27abd9725818b597bb6377b1a6fb Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Tue, 12 Mar 2019 22:37:33 -0700 Subject: [PATCH 24/68] Fix NPE caught in FallbackRecentsTest Change-Id: Ia586bebd9a24e5bbfcbed50c7c260134911e073b Fixes: 128464669 Test: N/A --- .../src/com/android/quickstep/views/RecentsView.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 2583ffb0d9..252a95708c 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 @@ -1578,7 +1578,9 @@ public abstract class RecentsView extends PagedView impl } public void updateLiveTileIcon(Drawable icon) { - mLiveTileOverlay.setIcon(icon); + if (mLiveTileOverlay != null) { + mLiveTileOverlay.setIcon(icon); + } } public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) { From 87beed6404d768da4bc71f371d8a80d124bd3a6d Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Wed, 13 Mar 2019 08:18:44 -0700 Subject: [PATCH 25/68] Handle other SystemShortcut view types. Bug: 127807969 Change-Id: Ifcc24e9261ddc730bcddd5724a230910daafce17 --- src/com/android/launcher3/Utilities.java | 6 ++++-- src/com/android/launcher3/views/FloatingIconView.java | 11 +++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index e699500e9c..7036639154 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -571,9 +571,11 @@ public final class Utilities { final boolean isFolderIcon = v instanceof FolderIcon; final Rect rect = new Rect(); + // Deep shortcut views have their icon drawn in a separate view. final boolean fromDeepShortcutView = v.getParent() instanceof DeepShortcutView; - if (fromDeepShortcutView) { - // Deep shortcut views have their icon drawn in a separate view. + if (v instanceof DeepShortcutView) { + dragLayer.getDescendantRectRelativeToSelf(((DeepShortcutView) v).getIconView(), rect); + } else if (fromDeepShortcutView) { DeepShortcutView view = (DeepShortcutView) v.getParent(); dragLayer.getDescendantRectRelativeToSelf(view.getIconView(), rect); } else if ((isBubbleTextView || isFolderIcon) && v.getTag() instanceof ItemInfo diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index 7af2bd87b0..e5c70da5f6 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -50,6 +50,7 @@ import com.android.launcher3.folder.FolderShape; import com.android.launcher3.graphics.ShiftedBitmapDrawable; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.popup.SystemShortcut; +import com.android.launcher3.shortcuts.DeepShortcutView; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; @@ -197,8 +198,14 @@ public class FloatingIconView extends View implements Animator.AnimatorListener, // Similar to DragView, we simply use the BubbleTextView icon here. drawable = ((BubbleTextView) v).getIcon(); } - if (v instanceof ImageView && info instanceof SystemShortcut) { - drawable = ((ImageView) v).getDrawable(); + if (info instanceof SystemShortcut) { + if (v instanceof ImageView) { + drawable = ((ImageView) v).getDrawable(); + } else if (v instanceof DeepShortcutView) { + drawable = ((DeepShortcutView) v).getIconView().getBackground(); + } else { + drawable = v.getBackground(); + } } if (drawable == null) { drawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height, From 100e1263e245654f9889b5a370093dd24fdd19ac Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 4 Mar 2019 10:55:13 -0800 Subject: [PATCH 26/68] Add thumbnails to the task view. This CL adds thumbnails to the task view so that the labels are next to a combined view with the thumbnail to the left and the icon at the bottom right. Due to the way launcher creates shadows on icons, the icons are actually smaller than desired, so there need to be further changes to determine the appropriate scale in order to have the bottom of the icons be flush with the snapshot's bottom. Bug: 114136250 Test: Launch l3GoIconRecents, see thumbnails + icon Change-Id: Ief1cf54634544b9e310c68588998901871efda44 --- go/quickstep/res/layout/task_item_view.xml | 20 ++++++++++++++---- go/quickstep/res/values/dimens.xml | 21 +++++++++++++++++++ .../src/com/android/quickstep/TaskHolder.java | 1 + .../android/quickstep/views/TaskItemView.java | 19 +++++++++++++++-- 4 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 go/quickstep/res/values/dimens.xml diff --git a/go/quickstep/res/layout/task_item_view.xml b/go/quickstep/res/layout/task_item_view.xml index 90940c4951..ee67d49fc7 100644 --- a/go/quickstep/res/layout/task_item_view.xml +++ b/go/quickstep/res/layout/task_item_view.xml @@ -19,12 +19,24 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> - + android:layout_marginHorizontal="8dp" + android:layout_marginVertical="@dimen/task_item_half_vert_margin"> + + + + + + 60dp + 8dp + 36dp + \ No newline at end of file diff --git a/go/quickstep/src/com/android/quickstep/TaskHolder.java b/go/quickstep/src/com/android/quickstep/TaskHolder.java index 67e8ece227..8d5e4d55f3 100644 --- a/go/quickstep/src/com/android/quickstep/TaskHolder.java +++ b/go/quickstep/src/com/android/quickstep/TaskHolder.java @@ -45,6 +45,7 @@ final class TaskHolder extends ViewHolder { mTask = task; mTaskItemView.setLabel(task.titleDescription); mTaskItemView.setIcon(task.icon); + mTaskItemView.setThumbnail(task.thumbnail.thumbnail); } /** diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java index ce3947d240..3818965b79 100644 --- a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java +++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java @@ -16,6 +16,7 @@ package com.android.quickstep.views; import android.content.Context; +import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.widget.ImageView; @@ -31,6 +32,7 @@ public final class TaskItemView extends LinearLayout { private TextView mLabelView; private ImageView mIconView; + private ImageView mThumbnailView; public TaskItemView(Context context, AttributeSet attrs) { super(context, attrs); @@ -40,7 +42,8 @@ public final class TaskItemView extends LinearLayout { protected void onFinishInflate() { super.onFinishInflate(); mLabelView = findViewById(R.id.task_label); - mIconView = findViewById(R.id.task_icon_and_thumbnail); + mThumbnailView = findViewById(R.id.task_thumbnail); + mIconView = findViewById(R.id.task_icon); } /** @@ -58,7 +61,19 @@ public final class TaskItemView extends LinearLayout { * @param icon task icon */ public void setIcon(Drawable icon) { + // TODO: Scale the icon up based off the padding on the side + // The icon proper is actually smaller than the drawable and has "padding" on the side for + // the purpose of drawing the shadow, allowing the icon to pop up, so we need to scale the + // view if we want the icon to be flush with the bottom of the thumbnail. mIconView.setImageDrawable(icon); - // TODO: Add in combination drawable for icon + thumbnail + } + + /** + * Set the task thumbnail for the task. + * + * @param thumbnail task thumbnail for the task + */ + public void setThumbnail(Bitmap thumbnail) { + mThumbnailView.setImageBitmap(thumbnail); } } From 1ccc31379e45c61bb4792e1d1c9d9d3cfaeedd90 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Wed, 13 Mar 2019 20:19:08 +0000 Subject: [PATCH 27/68] Revert "Temporarily make TouchInteractionService direct boot unaware" This reverts commit 3a4f503e5d96aea97b4c8ad4a47db7c7de431e8b. Reason for revert: SystemUI now expects our service to be direct boot aware Change-Id: I6f7906046e79f83848cb46f5cce63c827d9fbf4d --- quickstep/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml index 0992849980..62d0500160 100644 --- a/quickstep/AndroidManifest.xml +++ b/quickstep/AndroidManifest.xml @@ -39,7 +39,7 @@ + android:directBootAware="true" > From 3cb12c8d20c1431e3ffd39dd1775811dd5b61951 Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 13 Mar 2019 12:13:14 -0700 Subject: [PATCH 28/68] Fade to empty view when no tasks for Recents Go This CL adds a view for when there are no tasks and fades into/out of it appropriately based off changes to the adapter. Bug: 114136250 Test: Add task, remove task, see empty view shows up properly. Change-Id: I47d0bbfb19d56f5f0de5bec3c0ac2b5cfb63253f --- .../res/layout/icon_recents_root_view.xml | 12 +++- .../quickstep/views/IconRecentsView.java | 60 ++++++++++++++++++- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/go/quickstep/res/layout/icon_recents_root_view.xml b/go/quickstep/res/layout/icon_recents_root_view.xml index 6c5095000b..ff3dcef989 100644 --- a/go/quickstep/res/layout/icon_recents_root_view.xml +++ b/go/quickstep/res/layout/icon_recents_root_view.xml @@ -24,5 +24,15 @@ android:id="@+id/recent_task_recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" - android:scrollbars="none"/> + android:scrollbars="none" + android:visibility="gone"/> + \ No newline at end of file diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index b7740eb50b..8c7177babe 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -17,15 +17,19 @@ package com.android.quickstep.views; import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.util.AttributeSet; import android.util.FloatProperty; +import android.view.View; import android.view.ViewDebug; import android.widget.FrameLayout; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver; import com.android.launcher3.R; import com.android.quickstep.TaskAdapter; @@ -70,6 +74,7 @@ public final class IconRecentsView extends FrameLayout { return ALPHA.get(view); } }; + private static final long CROSSFADE_DURATION = 300; /** * A ratio representing the view's relative placement within its padded space. For example, 0 @@ -84,7 +89,7 @@ public final class IconRecentsView extends FrameLayout { private float mTranslationYFactor; private RecyclerView mTaskRecyclerView; - + private View mEmptyView; public IconRecentsView(Context context, AttributeSet attrs) { super(context, attrs); @@ -106,6 +111,19 @@ public final class IconRecentsView extends FrameLayout { ItemTouchHelper helper = new ItemTouchHelper( new TaskSwipeCallback(mTaskInputController)); helper.attachToRecyclerView(mTaskRecyclerView); + + mEmptyView = findViewById(R.id.recent_task_empty_view); + mTaskAdapter.registerAdapterDataObserver(new AdapterDataObserver() { + @Override + public void onChanged() { + updateContentViewVisibility(); + } + + @Override + public void onItemRangeRemoved(int positionStart, int itemCount) { + updateContentViewVisibility(); + } + }); } } @@ -133,4 +151,44 @@ public final class IconRecentsView extends FrameLayout { private float computeTranslationYForFactor(float translationYFactor) { return translationYFactor * (getPaddingBottom() - getPaddingTop()); } + + /** + * Update the content view so that the appropriate view is shown based off the current list + * of tasks. + */ + private void updateContentViewVisibility() { + int taskListSize = mTaskLoader.getCurrentTaskList().size(); + if (mEmptyView.getVisibility() != VISIBLE && taskListSize == 0) { + crossfadeViews(mEmptyView, mTaskRecyclerView); + // TODO: Go to home. + } + if (mTaskRecyclerView.getVisibility() != VISIBLE && taskListSize > 0) { + crossfadeViews(mTaskRecyclerView, mEmptyView); + } + } + + /** + * Animate views so that one view fades in while the other fades out. + * + * @param fadeInView view that should fade in + * @param fadeOutView view that should fade out + */ + private void crossfadeViews(View fadeInView, View fadeOutView) { + fadeInView.setVisibility(VISIBLE); + fadeInView.setAlpha(0f); + fadeInView.animate() + .alpha(1f) + .setDuration(CROSSFADE_DURATION) + .setListener(null); + + fadeOutView.animate() + .alpha(0f) + .setDuration(CROSSFADE_DURATION) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + fadeOutView.setVisibility(GONE); + } + }); + } } From 09df0831da83c6ab3e2d19ee18a26f1fdd9f525f Mon Sep 17 00:00:00 2001 From: vadimt Date: Thu, 7 Mar 2019 14:59:30 -0800 Subject: [PATCH 29/68] Creating provider for test gesture dimensions. Start with answering only whether the provider will answer any other questions. Bug: 123904290 Change-Id: I7b3ba8c7689f937d8bc1d470b00574ab4340130d --- Android.bp | 3 +- AndroidManifest-common.xml | 8 +++ .../launcher3/TestInformationProvider.java | 66 +++++++++++++++++++ src/com/android/launcher3/TestProtocol.java | 3 + tests/tapl/AndroidManifest.xml | 26 ++++++++ .../tapl/LauncherInstrumentation.java | 38 +++++++++-- 6 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 src/com/android/launcher3/TestInformationProvider.java create mode 100644 tests/tapl/AndroidManifest.xml diff --git a/Android.bp b/Android.bp index c5832441c7..1121a7962f 100644 --- a/Android.bp +++ b/Android.bp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -java_library_static { +android_library { name: "launcher-aosp-tapl", static_libs: [ "androidx.annotation_annotation", @@ -27,5 +27,6 @@ java_library_static { "src/com/android/launcher3/util/SecureSettingsObserver.java", "src/com/android/launcher3/TestProtocol.java", ], + manifest: "tests/tapl/AndroidManifest.xml", platform_apis: true, } diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml index 819e6bcc95..5a2d0fe834 100644 --- a/AndroidManifest-common.xml +++ b/AndroidManifest-common.xml @@ -152,6 +152,14 @@ android:writePermission="${packageName}.permission.WRITE_SETTINGS" android:readPermission="${packageName}.permission.READ_SETTINGS" /> + + + + + + + diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 93b4cc6054..7a27fa465f 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -18,10 +18,11 @@ package com.android.launcher3.tapl; import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME; -import android.app.ActivityManager; import android.app.Instrumentation; import android.app.UiAutomation; +import android.content.ContentResolver; import android.graphics.Point; +import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.os.SystemClock; @@ -43,6 +44,7 @@ import com.android.quickstep.SwipeUpSetting; import org.junit.Assert; +import java.io.IOException; import java.lang.ref.WeakReference; import java.util.List; import java.util.concurrent.TimeoutException; @@ -95,6 +97,7 @@ public final class LauncherInstrumentation { private final UiDevice mDevice; private final Instrumentation mInstrumentation; private int mExpectedRotation = Surface.ROTATION_0; + private final Uri mTestProviderUri; /** * Constructs the root of TAPL hierarchy. You get all other objects from it. @@ -103,11 +106,34 @@ public final class LauncherInstrumentation { mInstrumentation = instrumentation; mDevice = UiDevice.getInstance(instrumentation); - // Launcher should run in test harness so that custom accessibility protocol between - // Launcher and TAPL is enabled. In-process tests enable this protocol with a direct call - // into Launcher. - assertTrue("Device must run in a test harness", - TestHelpers.isInLauncherProcess() || ActivityManager.isRunningInTestHarness()); + final String testPackage = mInstrumentation.getContext().getPackageName(); + final String targetPackage = mInstrumentation.getTargetContext().getPackageName(); + + // Launcher package. As during inproc tests the tested launcher may not be selected as the + // current launcher, choosing target package for inproc. For out-of-proc, use the installed + // launcher package. + final String authorityPackage = testPackage.equals(targetPackage) ? + getLauncherPackageName() : + targetPackage; + + mTestProviderUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(authorityPackage + ".TestInfo") + .build(); + + try { + mDevice.executeShellCommand("pm grant " + testPackage + + " android.permission.WRITE_SECURE_SETTINGS"); + } catch (IOException e) { + fail(e.toString()); + } + + // Launcher should run in test harness so that custom test protocols between Launcher and + // TAPL are enabled. In-process tests enable this protocol with a direct call into Launcher. + final Bundle response = mInstrumentation.getContext().getContentResolver().call( + mTestProviderUri, TestProtocol.IS_TEST_INFO_ENABLED, null, null); + assertTrue("Launcher is not running in test harness", + response.getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, false)); } void setActiveContainer(VisibleContainer container) { From e7a1e25906709133e51aacea330af3ae0dd497d9 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 14 Mar 2019 01:16:28 +0000 Subject: [PATCH 30/68] Revert "Use GestureDetector in WorkspaceTouchListener" This reverts commit 7c8fb786fa529c76fc88b8b24dec2d39e225546b. Reason for revert: Chatted with Sunny, the concern was brought up that this is risky and will create potential for dogfood confusion with the new gesture navigation that is getting rolled out soon. So we can try to reland this in a few weeks. Change-Id: I4f09cc9982164195dc42cd6e4714ede767cce786 --- .../launcher3/touch/WorkspaceTouchListener.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java index 78017569e0..4de082e90e 100644 --- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java +++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java @@ -29,7 +29,6 @@ import android.graphics.Rect; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; -import android.view.GestureDetector; import android.view.View.OnTouchListener; import android.view.ViewConfiguration; @@ -46,7 +45,7 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; /** * Helper class to handle touch on empty space in workspace and show options popup on long press */ -public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListener implements OnTouchListener { +public class WorkspaceTouchListener implements OnTouchListener, Runnable { /** * STATE_PENDING_PARENT_INFORM is the state between longPress performed & the next motionEvent. @@ -67,21 +66,16 @@ public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListe private int mLongPressState = STATE_CANCELLED; - private final GestureDetector mGestureDetector; - public WorkspaceTouchListener(Launcher launcher, Workspace workspace) { mLauncher = launcher; mWorkspace = workspace; // Use twice the touch slop as we are looking for long press which is more // likely to cause movement. mTouchSlop = 2 * ViewConfiguration.get(launcher).getScaledTouchSlop(); - mGestureDetector = new GestureDetector(workspace.getContext(), this); } @Override public boolean onTouch(View view, MotionEvent ev) { - mGestureDetector.onTouchEvent(ev); - int action = ev.getActionMasked(); if (action == ACTION_DOWN) { // Check if we can handle long press. @@ -103,6 +97,7 @@ public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListe if (handleLongPress) { mLongPressState = STATE_REQUESTED; mTouchDownPoint.set(ev.getX(), ev.getY()); + mWorkspace.postDelayed(this, getLongPressTimeout()); } mWorkspace.onTouchEvent(ev); @@ -160,11 +155,12 @@ public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListe } private void cancelLongPress() { + mWorkspace.removeCallbacks(this); mLongPressState = STATE_CANCELLED; } @Override - public void onLongPress(MotionEvent event) { + public void run() { if (mLongPressState == STATE_REQUESTED) { if (canHandleLongPress()) { mLongPressState = STATE_PENDING_PARENT_INFORM; From c28aa1a1972622b7008d743521b836cb084ee95c Mon Sep 17 00:00:00 2001 From: vadimt Date: Mon, 11 Mar 2019 17:49:01 -0700 Subject: [PATCH 31/68] Adding logging for showing DWB toast We generate an event upon every invocation of Overview with at least one DWB toast. Bug: 123892673 Change-Id: Ia24f4be8e9f0f6ab6d31095b8367b73fb6c8cd7f --- protos/launcher_log.proto | 1 + .../android/quickstep/views/RecentsView.java | 19 +++++++++++++++++-- .../com/android/quickstep/views/TaskView.java | 3 +++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto index 4129ae8ad9..85f982684d 100644 --- a/protos/launcher_log.proto +++ b/protos/launcher_log.proto @@ -122,6 +122,7 @@ enum TipType { SWIPE_UP_TEXT = 2; QUICK_SCRUB_TEXT = 3; PREDICTION_TEXT = 4; + DWB_TOAST = 5; } // Used to define the action component of the LauncherEvent. 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 2583ffb0d9..aeb29536f2 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 @@ -22,13 +22,14 @@ import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; -import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; 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.uioverrides.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS; -import static com.android.quickstep.util.ClipAnimationHelper.TransformParams; import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR; +import static com.android.quickstep.util.ClipAnimationHelper.TransformParams; + import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.LayoutTransition; @@ -81,6 +82,7 @@ import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PropertyListBuilder; import com.android.launcher3.anim.SpringObjectAnimator; import com.android.launcher3.config.FeatureFlags; +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.OverScroller; @@ -100,6 +102,7 @@ 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; import com.android.systemui.shared.system.TaskStackChangeListener; @@ -167,6 +170,8 @@ public abstract class RecentsView extends PagedView impl private final ViewPool mTaskViewPool; + private boolean mDwbToastShown; + /** * TODO: Call reloadIdNeeded in onTaskStackChanged. */ @@ -427,6 +432,16 @@ public abstract class RecentsView extends PagedView impl public void setOverviewStateEnabled(boolean enabled) { mOverviewStateEnabled = enabled; updateTaskStackListenerState(); + if (!enabled) mDwbToastShown = false; + } + + public void onDigitalWellbeingToastShown() { + if (!mDwbToastShown) { + mDwbToastShown = true; + mActivity.getUserEventDispatcher().logActionTip( + LauncherEventUtil.VISIBLE, + LauncherLogProto.TipType.DWB_TOAST); + } } @Override 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 9eec584a27..98495db9ae 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 @@ -326,6 +326,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { (saturation, contentDescription) -> { setContentDescription(contentDescription); mSnapshotView.setSaturation(saturation); + if (mDigitalWellBeingToast.getVisibility() == VISIBLE) { + getRecentsView().onDigitalWellbeingToastShown(); + } }); }); } else { From 319b5a6acee269e7893fbdeda407de16ee05a0a0 Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Thu, 14 Mar 2019 13:41:55 -0700 Subject: [PATCH 32/68] Remove f/b based jars out of the Android.mk Benefit! we would not get merge conflict when one of these jars update and we need to do code merge. Change-Id: Ia6a4a632474a7b19aaede1d20d1373902da1400c --- Android.mk | 29 ----------------------------- libs/README.txt | 8 -------- libs/launcher_protos.jar | Bin 57919 -> 0 bytes libs/plugin_core.jar | Bin 5057 -> 0 bytes quickstep/libs/sysui_shared.jar | Bin 156330 -> 0 bytes 5 files changed, 37 deletions(-) delete mode 100644 libs/README.txt delete mode 100644 libs/launcher_protos.jar delete mode 100644 libs/plugin_core.jar delete mode 100644 quickstep/libs/sysui_shared.jar diff --git a/Android.mk b/Android.mk index 74b96d4edc..2feb1419a2 100644 --- a/Android.mk +++ b/Android.mk @@ -16,35 +16,6 @@ LOCAL_PATH := $(call my-dir) -# -# Prebuilt Java Libraries -# -include $(CLEAR_VARS) -LOCAL_MODULE := libSharedSystemUI -LOCAL_MODULE_TAGS := optional -LOCAL_MODULE_CLASS := JAVA_LIBRARIES -LOCAL_SRC_FILES := quickstep/libs/sysui_shared.jar -LOCAL_UNINSTALLABLE_MODULE := true -LOCAL_SDK_VERSION := current -include $(BUILD_PREBUILT) - -include $(CLEAR_VARS) -LOCAL_MODULE := libPluginCore -LOCAL_MODULE_TAGS := optional -LOCAL_MODULE_CLASS := JAVA_LIBRARIES -LOCAL_SRC_FILES := libs/plugin_core.jar -LOCAL_UNINSTALLABLE_MODULE := true -LOCAL_SDK_VERSION := current -include $(BUILD_PREBUILT) - -include $(CLEAR_VARS) -LOCAL_MODULE := libLauncherProtos -LOCAL_MODULE_TAGS := optional -LOCAL_MODULE_CLASS := JAVA_LIBRARIES -LOCAL_SRC_FILES := libs/launcher_protos.jar -LOCAL_UNINSTALLABLE_MODULE := true -LOCAL_SDK_VERSION := current -include $(BUILD_PREBUILT) # # Build rule for plugin lib (needed to write a plugin). # diff --git a/libs/README.txt b/libs/README.txt deleted file mode 100644 index 9109592561..0000000000 --- a/libs/README.txt +++ /dev/null @@ -1,8 +0,0 @@ -These jar are compiled in the frameworks/base of the platform tree. - -launcher_protos.jar is defined as launcherprotosnano in the following file: -frameworks/base/core/protos/android/stats/launcher/Android.bp - -plugin_core.jar is defined as PluginCoreLib in the following file: -frameworks/base/packages/SystemUI/plugin/Android.bp - diff --git a/libs/launcher_protos.jar b/libs/launcher_protos.jar deleted file mode 100644 index c0439363b867208522d54d8b4e54c3c822e98f3b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57919 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 HQ2zQKjPHyY diff --git a/libs/plugin_core.jar b/libs/plugin_core.jar deleted file mode 100644 index dd27f86fa3f803e79353f2e549caa72d9bdbad6b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5057 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 Ee@Xr(!2kdN diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar deleted file mode 100644 index 4b64ad4ade886defa136be622218da218b047426..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156330 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�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≫eOGqy8dv#hD_io1=W|4o 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 From 40f1d863f66a02ae5e89fc6178ce8ceb567fefcc Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 14 Mar 2019 18:03:55 -0500 Subject: [PATCH 33/68] Number of DEBUG_STATES was 1 less than necessary Change-Id: I0a9289f9bcf99df0d9a466ad3ff00996192921f2 --- .../src/com/android/quickstep/WindowTransformSwipeHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 00dbdff1bf..fd53f9c87c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -110,7 +110,7 @@ public class WindowTransformSwipeHandler implements SwipeAnimationListener, OnApplyWindowInsetsListener { private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName(); - private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null; + private static final String[] STATE_NAMES = DEBUG_STATES ? new String[17] : null; private static int getFlagForIndex(int index, String name) { if (DEBUG_STATES) { From 7143ba6fec67f676514b7a73ba0a21d53cce9ad1 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Fri, 15 Mar 2019 09:00:05 -0700 Subject: [PATCH 34/68] Make QSB_ON_FIRST_SCREEN a boolean. Bug: 118140522 Change-Id: Ic8e47e71f538b0b0caff6f630566665d05a65522 --- src/com/android/launcher3/Launcher.java | 7 +++---- src/com/android/launcher3/LauncherProvider.java | 2 +- src/com/android/launcher3/Workspace.java | 4 ++-- src/com/android/launcher3/config/BaseFlags.java | 5 ++--- .../launcher3/graphics/LauncherPreviewRenderer.java | 2 +- src/com/android/launcher3/model/BgDataModel.java | 2 +- src/com/android/launcher3/model/GridSizeMigrationTask.java | 3 +-- src/com/android/launcher3/model/LoaderCursor.java | 3 +-- src/com/android/launcher3/provider/ImportDataTask.java | 2 +- src/com/android/launcher3/qsb/QsbContainerView.java | 2 +- 10 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 99343aa2b3..f283a6c260 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1771,12 +1771,11 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, @Override public void bindScreens(IntArray orderedScreenIds) { // Make sure the first screen is always at the start. - if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() && + if (FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) { orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID); orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID); - } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get() - && orderedScreenIds.isEmpty()) { + } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) { // If there are no screens, we need to have an empty screen mWorkspace.addExtraEmptyScreen(); } @@ -1792,7 +1791,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, int count = orderedScreenIds.size(); for (int i = 0; i < count; i++) { int screenId = orderedScreenIds.get(i); - if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get() || screenId != Workspace.FIRST_SCREEN_ID) { + if (!FeatureFlags.QSB_ON_FIRST_SCREEN || screenId != Workspace.FIRST_SCREEN_ID) { // No need to bind the first screen, as its always bound. mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId); } diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index ce73229286..39d93c8a2d 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -729,7 +729,7 @@ public class LauncherProvider extends ContentProvider { convertShortcutsToLauncherActivities(db); case 26: // QSB was moved to the grid. Clear the first row on screen 0. - if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() && + if (FeatureFlags.QSB_ON_FIRST_SCREEN && !LauncherDbUtils.prepareScreenZeroToHostQsb(mContext, db)) { break; } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 9427675851..720a692361 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -487,7 +487,7 @@ public class Workspace extends PagedView * @param qsb an existing qsb to recycle or null. */ public void bindAndInitFirstWorkspaceScreen(View qsb) { - if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get()) { + if (!FeatureFlags.QSB_ON_FIRST_SCREEN) { return; } // Add the first page @@ -794,7 +794,7 @@ public class Workspace extends PagedView int id = mWorkspaceScreens.keyAt(i); CellLayout cl = mWorkspaceScreens.valueAt(i); // FIRST_SCREEN_ID can never be removed. - if ((!FeatureFlags.QSB_ON_FIRST_SCREEN.get() || id > FIRST_SCREEN_ID) + if ((!FeatureFlags.QSB_ON_FIRST_SCREEN || id > FIRST_SCREEN_ID) && cl.getShortcutsAndWidgets().getChildCount() == 0) { removeScreens.add(id); } diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java index cb239b3de1..c260fa333e 100644 --- a/src/com/android/launcher3/config/BaseFlags.java +++ b/src/com/android/launcher3/config/BaseFlags.java @@ -68,9 +68,8 @@ 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; - public static final TogglableFlag QSB_ON_FIRST_SCREEN = new TogglableFlag("QSB_ON_FIRST_SCREEN", - true, - "Enable moving the QSB on the 0th screen of the workspace"); + // Enable moving the QSB on the 0th screen of the workspace + public static final boolean QSB_ON_FIRST_SCREEN = true; public static final TogglableFlag EXAMPLE_FLAG = new TogglableFlag("EXAMPLE_FLAG", true, "An example flag that doesn't do anything. Useful for testing"); diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index 837749d16c..eb45be133e 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -262,7 +262,7 @@ public class LauncherPreviewRenderer implements Callable { } // Add first page QSB - if (FeatureFlags.QSB_ON_FIRST_SCREEN.get()) { + if (FeatureFlags.QSB_ON_FIRST_SCREEN) { View qsb = mHomeElementInflater.inflate( R.layout.search_container_workspace, mWorkspace, false); CellLayout.LayoutParams lp = diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index b338fff55d..e8cc8f9f4e 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -129,7 +129,7 @@ public class BgDataModel { screenSet.add(item.screenId); } } - if (FeatureFlags.QSB_ON_FIRST_SCREEN.get() || screenSet.isEmpty()) { + if (FeatureFlags.QSB_ON_FIRST_SCREEN || screenSet.isEmpty()) { screenSet.add(Workspace.FIRST_SCREEN_ID); } return screenSet.getArray(); diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java index 243b286411..faecc067e4 100644 --- a/src/com/android/launcher3/model/GridSizeMigrationTask.java +++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java @@ -253,8 +253,7 @@ public class GridSizeMigrationTask { */ protected void migrateScreen(int screenId) { // If we are migrating the first screen, do not touch the first row. - int startY = - (FeatureFlags.QSB_ON_FIRST_SCREEN.get() && screenId == Workspace.FIRST_SCREEN_ID) + int startY = (FeatureFlags.QSB_ON_FIRST_SCREEN && screenId == Workspace.FIRST_SCREEN_ID) ? 1 : 0; ArrayList items = loadWorkspaceEntries(screenId); diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index d104a8b22f..88193d03ab 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -442,8 +442,7 @@ public class LoaderCursor extends CursorWrapper { if (item.screenId == Workspace.FIRST_SCREEN_ID) { // Mark the first row as occupied (if the feature is enabled) // in order to account for the QSB. - screen.markCells(0, 0, countX + 1, 1, - FeatureFlags.QSB_ON_FIRST_SCREEN.get()); + screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN); } occupied.put(item.screenId, screen); } diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java index 86fcc06802..7b62f53fe6 100644 --- a/src/com/android/launcher3/provider/ImportDataTask.java +++ b/src/com/android/launcher3/provider/ImportDataTask.java @@ -104,7 +104,7 @@ public class ImportDataTask { .getSerialNumberForUser(Process.myUserHandle())); boolean createEmptyRowOnFirstScreen; - if (FeatureFlags.QSB_ON_FIRST_SCREEN.get()) { + if (FeatureFlags.QSB_ON_FIRST_SCREEN) { try (Cursor c = mContext.getContentResolver().query(mOtherFavoritesUri, null, // get items on the first row of the first screen (min screen id) "profileId = ? AND container = -100 AND cellY = 0 AND screen = " + diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java index c2bae6d3fd..857ea05d61 100644 --- a/src/com/android/launcher3/qsb/QsbContainerView.java +++ b/src/com/android/launcher3/qsb/QsbContainerView.java @@ -215,7 +215,7 @@ public class QsbContainerView extends FrameLayout { } public boolean isQsbEnabled() { - return FeatureFlags.QSB_ON_FIRST_SCREEN.get(); + return FeatureFlags.QSB_ON_FIRST_SCREEN; } protected Bundle createBindOptions() { From d64b4f44da58621ea9b5872e8e8ca2121e0abbe6 Mon Sep 17 00:00:00 2001 From: vadimt Date: Wed, 13 Mar 2019 13:50:17 -0700 Subject: [PATCH 35/68] Switching swipe-up to overview to using test info provider Also moving test provider to Quickstep to have access to the new info; and now Launcher3 doesn't have it. Bug: 123904290 Change-Id: I653376610e83839d102beb9c0604950dd314e8ba --- AndroidManifest-common.xml | 8 ----- .../launcher3/uioverrides/OverviewState.java | 6 ++-- quickstep/AndroidManifest.xml | 8 +++++ .../launcher3/uioverrides/OverviewState.java | 6 +++- .../quickstep}/TestInformationProvider.java | 32 ++++++++++++++++--- src/com/android/launcher3/TestProtocol.java | 5 ++- .../android/launcher3/tapl/Background.java | 27 ++++++++++------ .../tapl/LauncherInstrumentation.java | 23 +++++++++---- .../com/android/launcher3/tapl/Workspace.java | 11 +++++-- 9 files changed, 91 insertions(+), 35 deletions(-) rename {src/com/android/launcher3 => quickstep/src/com/android/quickstep}/TestInformationProvider.java (55%) diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml index 5a2d0fe834..819e6bcc95 100644 --- a/AndroidManifest-common.xml +++ b/AndroidManifest-common.xml @@ -152,14 +152,6 @@ android:writePermission="${packageName}.permission.WRITE_SETTINGS" android:readPermission="${packageName}.permission.READ_SETTINGS" /> - - - 3 12 + + + 200 + 30 diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index f5e5dd32cb..9c97c8c905 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -66,6 +66,6 @@ 24dp - 70dp - 200dp + 28dp + 70dp From 502847f7eb29cd8fc3f515e9e5aa2647b0d9e72f Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 13 Mar 2019 15:49:31 -0700 Subject: [PATCH 44/68] Add app to overview anim for Recents Go. This CL adds the first iteration of window animation to go from the app back to recents provided that the view is ready to be visible to the user. Bug: 114136250 Test: Go to recents, launch app, press overview to go back to recents Change-Id: Ic0567e7c87fa964bdad25d07eca61b78407a9ff5 --- go/quickstep/res/layout/task_item_view.xml | 8 +- go/quickstep/res/values/dimens.xml | 4 +- .../AppToOverviewAnimationProvider.java | 154 +++++++++++++++++- .../LauncherActivityControllerHelper.java | 7 +- .../com/android/quickstep/TaskAdapter.java | 24 +++ .../quickstep/views/IconRecentsView.java | 15 ++ .../android/quickstep/views/TaskItemView.java | 5 + 7 files changed, 204 insertions(+), 13 deletions(-) diff --git a/go/quickstep/res/layout/task_item_view.xml b/go/quickstep/res/layout/task_item_view.xml index ee67d49fc7..048e9c510a 100644 --- a/go/quickstep/res/layout/task_item_view.xml +++ b/go/quickstep/res/layout/task_item_view.xml @@ -21,15 +21,15 @@ android:orientation="horizontal"> - 60dp 8dp + 60dp + 60dp + 36dp 36dp \ No newline at end of file diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java index f199643bce..defed845db 100644 --- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -15,15 +15,29 @@ */ package com.android.quickstep; +import static com.android.launcher3.anim.Interpolators.ACCEL_2; +import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; +import static com.android.quickstep.util.RemoteAnimationProvider.getLayer; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; import android.animation.AnimatorSet; import android.animation.ValueAnimator; +import android.graphics.Matrix; +import android.graphics.Rect; import android.util.Log; +import android.view.View; + +import androidx.annotation.NonNull; import com.android.launcher3.BaseDraggingActivity; +import com.android.quickstep.util.MultiValueUpdateListener; import com.android.quickstep.util.RemoteAnimationProvider; +import com.android.quickstep.views.IconRecentsView; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; /** * Provider for the atomic remote window animation from the app to the overview. @@ -33,13 +47,17 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat; final class AppToOverviewAnimationProvider implements RemoteAnimationProvider { - private static final long RECENTS_LAUNCH_DURATION = 250; + private static final long APP_TO_THUMBNAIL_FADE_DURATION = 50; + private static final long APP_SCALE_DOWN_DURATION = 400; private static final String TAG = "AppToOverviewAnimationProvider"; private final ActivityControlHelper mHelper; + private final int mTargetTaskId; + private IconRecentsView mRecentsView; AppToOverviewAnimationProvider(ActivityControlHelper helper, int targetTaskId) { mHelper = helper; + mTargetTaskId = targetTaskId; } /** @@ -54,35 +72,157 @@ final class AppToOverviewAnimationProvider imple false /* animate activity */, (controller) -> { controller.dispatchOnStart(); ValueAnimator anim = controller.getAnimationPlayer() - .setDuration(RECENTS_LAUNCH_DURATION); + .setDuration(getRecentsLaunchDuration()); anim.setInterpolator(FAST_OUT_SLOW_IN); anim.start(); }); factory.onRemoteAnimationReceived(null); - factory.createActivityController(RECENTS_LAUNCH_DURATION); + factory.createActivityController(getRecentsLaunchDuration()); + mRecentsView = activity.getOverviewPanel(); return false; } /** - * Create remote window animation from the currently running app to the overview panel. + * Create remote window animation from the currently running app to the overview panel. Should + * be called after {@link #onActivityReady}. * * @param targetCompats the target apps * @return animation from app to overview */ @Override public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) { - //TODO: Implement the overview to app window animation for Go. AnimatorSet anim = new AnimatorSet(); - anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION)); + if (mRecentsView == null) { + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "No recents view. Using stub animation."); + } + anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration())); + return anim; + } + + RemoteAnimationTargetCompat recentsTarget = null; + RemoteAnimationTargetCompat closingAppTarget = null; + + for (RemoteAnimationTargetCompat target : targetCompats) { + if (target.mode == MODE_OPENING) { + recentsTarget = target; + } else if (target.mode == MODE_CLOSING && target.taskId == mTargetTaskId) { + closingAppTarget = target; + } + } + + if (closingAppTarget == null) { + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "No closing app target. Using stub animation."); + } + anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration())); + return anim; + } + if (recentsTarget == null) { + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "No recents target. Using stub animation."); + } + anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration())); + return anim; + } + + View thumbnailView = mRecentsView.getThumbnailViewForTask(mTargetTaskId); + if (thumbnailView == null) { + // TODO: We should either 1) guarantee the view is loaded before attempting this + // or 2) have a backup animation. + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "No thumbnail view for running task. Using stub animation."); + } + anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration())); + return anim; + } + + playAppScaleDownAnim(anim, closingAppTarget, recentsTarget, thumbnailView); + return anim; } + /** + * Animate a closing app to scale down to the location of the thumbnail view in recents. + * + * @param anim animator set + * @param appTarget the app surface thats closing + * @param recentsTarget the surface containing recents + * @param thumbnailView the thumbnail view to animate to + */ + private void playAppScaleDownAnim(@NonNull AnimatorSet anim, + @NonNull RemoteAnimationTargetCompat appTarget, + @NonNull RemoteAnimationTargetCompat recentsTarget, @NonNull View thumbnailView) { + + // Identify where the entering remote app should animate to. + Rect endRect = new Rect(); + thumbnailView.getGlobalVisibleRect(endRect); + + Rect appBounds = appTarget.sourceContainerBounds; + + ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1); + valueAnimator.setDuration(APP_SCALE_DOWN_DURATION); + + SyncRtSurfaceTransactionApplierCompat surfaceApplier = + new SyncRtSurfaceTransactionApplierCompat(thumbnailView); + + // Keep recents visible throughout the animation. + SurfaceParams[] params = new SurfaceParams[2]; + params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */, + null /* windowCrop */, getLayer(recentsTarget, MODE_OPENING), 0 /* cornerRadius */); + + valueAnimator.addUpdateListener(new MultiValueUpdateListener() { + private final FloatProp mScaleX; + private final FloatProp mScaleY; + private final FloatProp mTranslationX; + private final FloatProp mTranslationY; + private final FloatProp mAlpha; + + { + // Scale down and move to view location. + float endScaleX = ((float) endRect.width()) / appBounds.width(); + mScaleX = new FloatProp(1f, endScaleX, 0, APP_SCALE_DOWN_DURATION, + ACCEL_DEACCEL); + float endScaleY = ((float) endRect.height()) / appBounds.height(); + mScaleY = new FloatProp(1f, endScaleY, 0, APP_SCALE_DOWN_DURATION, + ACCEL_DEACCEL); + float endTranslationX = endRect.left - + (appBounds.width() - thumbnailView.getWidth()) / 2.0f; + mTranslationX = new FloatProp(0, endTranslationX, 0, APP_SCALE_DOWN_DURATION, + ACCEL_DEACCEL); + float endTranslationY = endRect.top - + (appBounds.height() - thumbnailView.getHeight()) / 2.0f; + mTranslationY = new FloatProp(0, endTranslationY, 0, APP_SCALE_DOWN_DURATION, + ACCEL_DEACCEL); + + // Fade out quietly near the end to be replaced by the real view. + mAlpha = new FloatProp(1.0f, 0, + APP_SCALE_DOWN_DURATION - APP_TO_THUMBNAIL_FADE_DURATION, + APP_TO_THUMBNAIL_FADE_DURATION, ACCEL_2); + } + + @Override + public void onUpdate(float percent) { + Matrix m = new Matrix(); + m.setScale(mScaleX.value, mScaleY.value, + appBounds.width() / 2.0f, appBounds.height() / 2.0f); + m.postTranslate(mTranslationX.value, mTranslationY.value); + + params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, m, + null /* windowCrop */, getLayer(appTarget, MODE_CLOSING), + 0 /* cornerRadius */); + surfaceApplier.scheduleApply(params); + } + }); + anim.play(valueAnimator); + } + /** * Get duration of animation from app to overview. * * @return duration of animation */ long getRecentsLaunchDuration() { - return RECENTS_LAUNCH_DURATION; + return APP_SCALE_DOWN_DURATION; } } diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java index d55d2e5fd9..40db7ddbce 100644 --- a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -21,6 +21,7 @@ import static com.android.launcher3.LauncherState.OVERVIEW; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherInitListener; +import com.android.launcher3.LauncherState; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.quickstep.views.IconRecentsView; @@ -38,10 +39,14 @@ public final class LauncherActivityControllerHelper extends GoActivityControlHel public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible, boolean animateActivity, Consumer callback) { + LauncherState fromState = activity.getStateManager().getState(); //TODO: Implement this based off where the recents view needs to be for app => recents anim. return new AnimationFactory() { @Override - public void createActivityController(long transitionLength) {} + public void createActivityController(long transitionLength) { + callback.accept(activity.getStateManager().createAnimationToNewWorkspace( + fromState, OVERVIEW, transitionLength)); + } @Override public void onTransitionCancelled() {} diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java index 4f3d1e4a03..9a2c0f88ab 100644 --- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java +++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java @@ -15,10 +15,12 @@ */ package com.android.quickstep; +import android.util.ArrayMap; import android.view.LayoutInflater; import android.view.ViewGroup; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView.Adapter; import com.android.launcher3.R; @@ -36,6 +38,7 @@ public final class TaskAdapter extends Adapter { private static final int MAX_TASKS_TO_DISPLAY = 6; private static final String TAG = "TaskAdapter"; private final TaskListLoader mLoader; + private final ArrayMap mTaskIdToViewMap = new ArrayMap<>(); private TaskInputController mInputController; public TaskAdapter(@NonNull TaskListLoader loader) { @@ -46,6 +49,16 @@ public final class TaskAdapter extends Adapter { mInputController = inputController; } + /** + * Get task item view for a given task id if it's attached to the view. + * + * @param taskId task id to search for + * @return corresponding task item view if it's attached, null otherwise + */ + public @Nullable TaskItemView getTaskItemView(int taskId) { + return mTaskIdToViewMap.get(taskId); + } + @Override public TaskHolder onCreateViewHolder(ViewGroup parent, int viewType) { TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext()) @@ -63,6 +76,17 @@ public final class TaskAdapter extends Adapter { return; } holder.bindTask(tasks.get(position)); + + } + + @Override + public void onViewAttachedToWindow(@NonNull TaskHolder holder) { + mTaskIdToViewMap.put(holder.getTask().key.id, (TaskItemView) holder.itemView); + } + + @Override + public void onViewDetachedFromWindow(@NonNull TaskHolder holder) { + mTaskIdToViewMap.remove(holder.getTask().key.id); } @Override diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index e294282c3e..3fdaefe156 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -27,6 +27,7 @@ import android.view.ViewDebug; import android.widget.FrameLayout; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -155,6 +156,20 @@ public final class IconRecentsView extends FrameLayout { }); } + /** + * Get the thumbnail view associated with a task for the purposes of animation. + * + * @param taskId task id of thumbnail view to get + * @return the thumbnail view for the task if attached, null otherwise + */ + public @Nullable View getThumbnailViewForTask(int taskId) { + TaskItemView view = mTaskAdapter.getTaskItemView(taskId); + if (view == null) { + return null; + } + return view.getThumbnailView(); + } + public void setTranslationYFactor(float translationFactor) { mTranslationYFactor = translationFactor; setTranslationY(computeTranslationYForFactor(mTranslationYFactor)); diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java index 3818965b79..373f107910 100644 --- a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java +++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java @@ -19,6 +19,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -76,4 +77,8 @@ public final class TaskItemView extends LinearLayout { public void setThumbnail(Bitmap thumbnail) { mThumbnailView.setImageBitmap(thumbnail); } + + public View getThumbnailView() { + return mThumbnailView; + } } From 687ff6afe112815ddaffafefabb2ad17a2d63115 Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 14 Mar 2019 13:11:38 -0500 Subject: [PATCH 45/68] Fix some state issues with home and quick switch gestures Handle the fact that LAUNCHER_STARTED can come 25ms+ later than LAUNCHER_PRESENT - Don't call prepareRecentsUi() if we already completed the gesture to go home - Previously it was possible to get LAUNCHER_PRESENT -> GESTURE_STARTED -> GESTURE_COMPLETED -> LAUNCHER_STARTED. Because we go to BACKGROUND_APP state in LAUNCHER_STARTED, this sequence ended up there instead of home (b/124338231) - Call setupRecentsViewUi() in LAUNCHER_PRESENT instead of LAUNCHER_START - Because setupRecentsViewUi() sets RecentsView to show the running task, it was interferring with quick switches that had the state sequence above Other quick switch fixes - Set canBeContinued = true for LAST_TASK gesture target. This handles cases like: switch from task 0 to task 1, intercept and go back to 0 (LAST_TASK), intercept again to go to 1 - Ignore deferred touch down target if we're continuing the previous gesture - Set STATE_HANDLER_INVALIDATED as soon as we start finishing recents animation instead of waiting until the new task is launched. This is what we do when re-launching the last task, and it allows us to start a new gesture sooner. One race condition still exists with consecutive quick switches: if you switch to a new task, the next gesture might get stuck if it starts after finishing the recents animation but before the task is fully launched/launcher is stopped. Bug: 124338231 Bug: 111926330 Change-Id: Ia7e1eebe6e81e2f10bb42a10b2f46fd720da0dc1 --- .../quickstep/OtherActivityInputConsumer.java | 6 +-- .../WindowTransformSwipeHandler.java | 48 ++++++++++--------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java index 4792cc7916..9506bfa923 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java @@ -130,7 +130,8 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC mVelocityTracker = VelocityTracker.obtain(); mActivityControlHelper = activityControl; - mIsDeferredDownTarget = isDeferredDownTarget; + boolean continuingPreviousGesture = swipeSharedState.getActiveListener() != null; + mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget; mOverviewCallbacks = overviewCallbacks; mTaskOverlayFactory = taskOverlayFactory; mInputConsumer = inputConsumer; @@ -142,8 +143,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx(); mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx(); - // If active listener isn't null, we are continuing the previous gesture. - mPassedTouchSlop = mPassedDragSlop = mSwipeSharedState.getActiveListener() != null; + mPassedTouchSlop = mPassedDragSlop = continuingPreviousGesture; } @Override 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 efc228b596..c2bbf5c626 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -162,8 +162,6 @@ public class WindowTransformSwipeHandler private static final int LAUNCHER_UI_STATES = STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED; - // For debugging, keep in sync with above states - enum GestureEndTarget { HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE), @@ -172,7 +170,7 @@ public class WindowTransformSwipeHandler NEW_TASK(0, STATE_START_NEW_TASK, false, true, ContainerType.APP), - LAST_TASK(0, STATE_SCALED_CONTROLLER_LAST_TASK, false, false, ContainerType.APP); + LAST_TASK(0, STATE_SCALED_CONTROLLER_LAST_TASK, false, true, ContainerType.APP); GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued, int containerType) { @@ -234,6 +232,7 @@ public class WindowTransformSwipeHandler private ThumbnailData mTaskSnapshot; private MultiStateCallback mStateCallback; + // Used to control launcher components throughout the swipe gesture. private AnimatorPlaybackController mLauncherTransitionController; private T mActivity; @@ -274,10 +273,8 @@ public class WindowTransformSwipeHandler private void initStateCallbacks() { mStateCallback = new MultiStateCallback(STATE_NAMES); - // Re-setup the recents UI when gesture starts, as the state could have been changed during - // that time by a previous window transition. - mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_GESTURE_STARTED, - this::setupRecentsViewUi); + mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED, + this::onLauncherPresentAndGestureStarted); mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED, this::initializeLauncherAnimationController); @@ -285,9 +282,6 @@ public class WindowTransformSwipeHandler mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN, this::launcherFrameDrawn); - mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED, - this::notifyGestureStartedAsync); - mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED | STATE_GESTURE_CANCELLED, this::resetStateForAnimationCancel); @@ -414,6 +408,8 @@ public class WindowTransformSwipeHandler } else { activity.setOnStartCallback(this::onLauncherStart); } + + setupRecentsViewUi(); return true; } @@ -425,8 +421,12 @@ public class WindowTransformSwipeHandler return; } - mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity, - mWasLauncherAlreadyVisible, true, this::onAnimatorPlaybackControllerCreated); + // 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) { + mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity, + mWasLauncherAlreadyVisible, true, this::onAnimatorPlaybackControllerCreated); + } AbstractFloatingView.closeAllOpenViews(activity, mWasLauncherAlreadyVisible); if (mWasLauncherAlreadyVisible) { @@ -450,11 +450,18 @@ public class WindowTransformSwipeHandler }); } - setupRecentsViewUi(); activity.getRootView().setOnApplyWindowInsetsListener(this); mStateCallback.setState(STATE_LAUNCHER_STARTED); } + private void onLauncherPresentAndGestureStarted() { + // Re-setup the recents UI when gesture starts, as the state could have been changed during + // that time by a previous window transition. + setupRecentsViewUi(); + + notifyGestureStartedAsync(); + } + private void setupRecentsViewUi() { if (mContinuingLastGesture) { return; @@ -723,7 +730,7 @@ public class WindowTransformSwipeHandler final int lastTaskIndex = mRecentsView.getTaskViewCount() - 1; final int runningTaskIndex = mRecentsView.getRunningTaskIndex(); taskToLaunch = nextPage <= lastTaskIndex ? nextPage : lastTaskIndex; - goingToNewTask = mRecentsView != null && taskToLaunch != runningTaskIndex; + goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex; } else { goingToNewTask = false; } @@ -859,8 +866,8 @@ public class WindowTransformSwipeHandler RecentsModel.INSTANCE.get(mContext).endStabilizationSession(); } - HomeAnimationFactory homeAnimFactory; if (mGestureEndTarget == HOME) { + HomeAnimationFactory homeAnimFactory; if (mActivity != null) { homeAnimFactory = mActivityControlHelper.prepareHomeUI(mActivity); } else { @@ -901,7 +908,6 @@ public class WindowTransformSwipeHandler } }); windowAnim.start(); - homeAnimFactory = null; } // Always play the entire launcher animation when going home, since it is separate from // the animation that has been controlled thus far. @@ -951,6 +957,7 @@ public class WindowTransformSwipeHandler } AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome(); + // 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 ? 0.75f : 1f; @@ -1006,18 +1013,15 @@ public class WindowTransformSwipeHandler // 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, - result -> setStateOnUiThread(STATE_HANDLER_INVALIDATED), - mMainThreadHandler); + mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false); } else { mRecentsAnimationWrapper.finish(true /* toRecents */, () -> { - mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false, - result -> setStateOnUiThread(STATE_HANDLER_INVALIDATED), - mMainThreadHandler); + mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false); }); } TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false); doLogGesture(NEW_TASK); + reset(); } public void reset() { From 096edde05678b221ee8ff5af8064e73f5dd6b3c6 Mon Sep 17 00:00:00 2001 From: Tony Date: Tue, 19 Mar 2019 11:08:54 -0500 Subject: [PATCH 46/68] Remove redundant resumeLastTaskForQuickstep() and use resumeLastTask() directly Also remove STATE_SCALED_CONTROLLER_LAST_TASK, which is redundant with STATE_RESUME_LAST_TASK. Basically this removes a path of unnecessary indirection now that everything is on the UI thread. Change-Id: If11fea2d6064ba909a439b9b88d7c80fb1ad9d73 --- .../WindowTransformSwipeHandler.java | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) 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 c2bbf5c626..eb1e7b4077 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -110,7 +110,7 @@ public class WindowTransformSwipeHandler implements SwipeAnimationListener, OnApplyWindowInsetsListener { private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName(); - private static final String[] STATE_NAMES = DEBUG_STATES ? new String[17] : null; + private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null; private static int getFlagForIndex(int index, String name) { if (DEBUG_STATES) { @@ -133,31 +133,29 @@ public class WindowTransformSwipeHandler getFlagForIndex(4, "STATE_SCALED_CONTROLLER_HOME"); private static final int STATE_SCALED_CONTROLLER_RECENTS = getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS"); - private static final int STATE_SCALED_CONTROLLER_LAST_TASK = - getFlagForIndex(6, "STATE_SCALED_CONTROLLER_LAST_TASK"); private static final int STATE_HANDLER_INVALIDATED = - getFlagForIndex(7, "STATE_HANDLER_INVALIDATED"); + getFlagForIndex(6, "STATE_HANDLER_INVALIDATED"); private static final int STATE_GESTURE_STARTED = - getFlagForIndex(8, "STATE_GESTURE_STARTED"); + getFlagForIndex(7, "STATE_GESTURE_STARTED"); private static final int STATE_GESTURE_CANCELLED = - getFlagForIndex(9, "STATE_GESTURE_CANCELLED"); + getFlagForIndex(8, "STATE_GESTURE_CANCELLED"); private static final int STATE_GESTURE_COMPLETED = - getFlagForIndex(10, "STATE_GESTURE_COMPLETED"); + getFlagForIndex(9, "STATE_GESTURE_COMPLETED"); private static final int STATE_CAPTURE_SCREENSHOT = - getFlagForIndex(11, "STATE_CAPTURE_SCREENSHOT"); + getFlagForIndex(10, "STATE_CAPTURE_SCREENSHOT"); private static final int STATE_SCREENSHOT_CAPTURED = - getFlagForIndex(12, "STATE_SCREENSHOT_CAPTURED"); + getFlagForIndex(11, "STATE_SCREENSHOT_CAPTURED"); private static final int STATE_SCREENSHOT_VIEW_SHOWN = - getFlagForIndex(13, "STATE_SCREENSHOT_VIEW_SHOWN"); + getFlagForIndex(12, "STATE_SCREENSHOT_VIEW_SHOWN"); private static final int STATE_RESUME_LAST_TASK = - getFlagForIndex(14, "STATE_RESUME_LAST_TASK"); + getFlagForIndex(13, "STATE_RESUME_LAST_TASK"); private static final int STATE_START_NEW_TASK = - getFlagForIndex(15, "STATE_START_NEW_TASK"); + getFlagForIndex(14, "STATE_START_NEW_TASK"); private static final int STATE_CURRENT_TASK_FINISHED = - getFlagForIndex(16, "STATE_CURRENT_TASK_FINISHED"); + getFlagForIndex(15, "STATE_CURRENT_TASK_FINISHED"); private static final int LAUNCHER_UI_STATES = STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED; @@ -170,7 +168,7 @@ public class WindowTransformSwipeHandler NEW_TASK(0, STATE_START_NEW_TASK, false, true, ContainerType.APP), - LAST_TASK(0, STATE_SCALED_CONTROLLER_LAST_TASK, false, true, ContainerType.APP); + LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP); GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued, int containerType) { @@ -289,8 +287,6 @@ public class WindowTransformSwipeHandler mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED, this::sendRemoteAnimationsToAnimationFactory); - mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SCALED_CONTROLLER_LAST_TASK, - this::resumeLastTaskForQuickstep); mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED, this::resumeLastTask); mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_APP_CONTROLLER_RECEIVED, @@ -320,8 +316,7 @@ public class WindowTransformSwipeHandler mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, this::invalidateHandler); mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED, this::invalidateHandlerWithLauncher); - mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED - | STATE_SCALED_CONTROLLER_LAST_TASK, + mStateCallback.addCallback(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK, this::notifyTransitionCancelled); mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED, @@ -814,7 +809,7 @@ public class WindowTransformSwipeHandler } } else if (endTarget == NEW_TASK || endTarget == LAST_TASK) { // Let RecentsView handle the scrolling to the task, which we launch in startNewTask() - // or resumeLastTaskForQuickstep(). + // or resumeLastTask(). if (mRecentsView != null) { duration = Math.max(duration, mRecentsView.getScroller().getDuration()); } @@ -995,17 +990,12 @@ public class WindowTransformSwipeHandler return anim; } - @UiThread - private void resumeLastTaskForQuickstep() { - setStateOnUiThread(STATE_RESUME_LAST_TASK); - doLogGesture(LAST_TASK); - reset(); - } - @UiThread private void resumeLastTask() { mRecentsAnimationWrapper.finish(false /* toRecents */, null); TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false); + doLogGesture(LAST_TASK); + reset(); } @UiThread From 6fe4e93d4a6802433241b7030c33a2e162f9280d Mon Sep 17 00:00:00 2001 From: vadimt Date: Tue, 19 Mar 2019 12:16:39 -0700 Subject: [PATCH 47/68] Stop using magic constant 0.6 for returning from all apps to overview Bug: 123904290 Change-Id: If1b6caf562484649ee107532ffe21d25deff4b97 --- .../quickstep/TestInformationProvider.java | 18 ++++++++++++++++++ src/com/android/launcher3/TestProtocol.java | 2 ++ .../launcher3/tapl/AllAppsFromOverview.java | 8 +++++++- .../com/android/launcher3/tapl/Background.java | 8 ++++---- .../tapl/LauncherInstrumentation.java | 5 +++++ 5 files changed, 36 insertions(+), 5 deletions(-) diff --git a/quickstep/src/com/android/quickstep/TestInformationProvider.java b/quickstep/src/com/android/quickstep/TestInformationProvider.java index 0c478d25e2..0c93dd6cf5 100644 --- a/quickstep/src/com/android/quickstep/TestInformationProvider.java +++ b/quickstep/src/com/android/quickstep/TestInformationProvider.java @@ -25,6 +25,9 @@ import android.os.Bundle; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherState; import com.android.launcher3.TestProtocol; import com.android.launcher3.Utilities; import com.android.launcher3.uioverrides.OverviewState; @@ -82,6 +85,21 @@ public class TestInformationProvider extends ContentProvider { response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight); break; } + case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: { + final LauncherAppState launcherAppState = + LauncherAppState.getInstanceNoCreate(); + if (launcherAppState == null) return null; + + final Launcher launcher = (Launcher) launcherAppState.getModel().getCallback(); + if (launcher == null) return null; + + final float progress = LauncherState.OVERVIEW.getVerticalProgress(launcher) + - LauncherState.ALL_APPS.getVerticalProgress(launcher); + final float distance = + launcher.getAllAppsController().getShiftRange() * progress; + response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance); + break; + } } return response; } diff --git a/src/com/android/launcher3/TestProtocol.java b/src/com/android/launcher3/TestProtocol.java index 5f752cc704..a4e4227153 100644 --- a/src/com/android/launcher3/TestProtocol.java +++ b/src/com/android/launcher3/TestProtocol.java @@ -36,4 +36,6 @@ public final class TestProtocol { "home-to-overview-swipe-height"; public static final String REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT = "background-to-overview-swipe-height"; + public static final String REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT = + "all-apps-to-overview-swipe-height"; } diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java index 264281502b..dcc51b50ba 100644 --- a/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java +++ b/tests/tapl/com/android/launcher3/tapl/AllAppsFromOverview.java @@ -23,6 +23,8 @@ import android.graphics.Point; import androidx.annotation.NonNull; import androidx.test.uiautomator.UiObject2; +import com.android.launcher3.TestProtocol; + /** * Operations on AllApps opened from Overview. */ @@ -45,7 +47,11 @@ public final class AllAppsFromOverview extends AllApps { final UiObject2 qsb = mLauncher.waitForObjectInContainer( allAppsContainer, "search_container_all_apps"); final Point start = qsb.getVisibleCenter(); - final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.6); + final int swipeHeight = mLauncher.getTestInfo( + TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT). + getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); + + final int endY = start.y + swipeHeight + mLauncher.getTouchSlop(); LauncherInstrumentation.log("AllAppsFromOverview.switchBackToOverview before swipe"); mLauncher.swipe(start.x, start.y, start.x, endY, OVERVIEW_STATE_ORDINAL); diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java index 7031cd34c0..2cde8ecd24 100644 --- a/tests/tapl/com/android/launcher3/tapl/Background.java +++ b/tests/tapl/com/android/launcher3/tapl/Background.java @@ -22,8 +22,6 @@ import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName; import static org.junit.Assert.assertTrue; -import android.view.ViewConfiguration; - import androidx.annotation.NonNull; import androidx.test.uiautomator.By; import androidx.test.uiautomator.Until; @@ -67,9 +65,11 @@ public class Background extends LauncherInstrumentation.VisibleContainer { final int swipeHeight = mLauncher.getTestInfo( getSwipeHeightRequestName()). getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); - final int slop = ViewConfiguration.get(mLauncher.getContext()).getScaledTouchSlop(); - mLauncher.swipe(centerX, startY, centerX, startY - swipeHeight - slop, expectedState); + mLauncher.swipe( + centerX, startY, centerX, + startY - swipeHeight - mLauncher.getTouchSlop(), + expectedState); } else { mLauncher.waitForSystemUiObject("recent_apps").click(); } diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index ce37348d3d..e3850ff0fd 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -32,6 +32,7 @@ import android.provider.Settings; import android.util.Log; import android.view.MotionEvent; import android.view.Surface; +import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; import androidx.annotation.NonNull; @@ -468,4 +469,8 @@ public final class LauncherInstrumentation { float getDisplayDensity() { return mInstrumentation.getTargetContext().getResources().getDisplayMetrics().density; } + + int getTouchSlop() { + return ViewConfiguration.get(getContext()).getScaledTouchSlop(); + } } \ No newline at end of file From 2bd3a0225b373c21774e7f0806ff49853d410042 Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Tue, 12 Mar 2019 23:36:47 -0700 Subject: [PATCH 48/68] Properly clean up screenshot of recents animation upon cancelation Bug: 122593881 Test: Manual Change-Id: Ia9ce1ede08309a0898c622bcd07a9e076443d98a --- .../quickstep/RecentsAnimationWrapper.java | 6 +++ .../WindowTransformSwipeHandler.java | 1 + .../util/RecentsAnimationListenerSet.java | 15 +++++- .../quickstep/views/LauncherRecentsView.java | 7 +-- .../android/quickstep/views/RecentsView.java | 48 ------------------- .../android/quickstep/views/TaskMenuView.java | 8 +--- .../com/android/quickstep/views/TaskView.java | 5 +- 7 files changed, 25 insertions(+), 65 deletions(-) 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 f0bc223ded..0924f38dcf 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java @@ -173,6 +173,12 @@ public class RecentsAnimationWrapper { return true; } + public void setCancelWithDeferredScreenshot(boolean deferredWithScreenshot) { + if (targetSet != null) { + targetSet.controller.setCancelWithDeferredScreenshot(deferredWithScreenshot); + } + } + public SwipeAnimationTargetSet getController() { return targetSet; } 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 7dc58a58c3..41c44601a0 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -1170,6 +1170,7 @@ public class WindowTransformSwipeHandler mLauncherTransitionController = null; } mActivityControlHelper.onSwipeUpComplete(mActivity); + mRecentsAnimationWrapper.setCancelWithDeferredScreenshot(true); // Animate the first icon. mRecentsView.animateUpRunningTaskIconScale(); 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 62f2183b50..94e704a716 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 @@ -38,9 +38,16 @@ 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 + // after app transition is finished. This delay is introduced to cover the app transition + // period of time. + private final int TRANSITION_DELAY = 100; + private final Set mListeners = new ArraySet<>(); private final boolean mShouldMinimizeSplitScreen; private final Consumer mOnFinishListener; + private RecentsAnimationControllerCompat mController; public RecentsAnimationListenerSet(boolean shouldMinimizeSplitScreen, Consumer onFinishListener) { @@ -64,6 +71,7 @@ public class RecentsAnimationListenerSet implements RecentsAnimationListener { public final void onAnimationStart(RecentsAnimationControllerCompat controller, RemoteAnimationTargetCompat[] targets, Rect homeContentInsets, Rect minimizedHomeBounds) { + mController = controller; SwipeAnimationTargetSet targetSet = new SwipeAnimationTargetSet(controller, targets, homeContentInsets, minimizedHomeBounds, mShouldMinimizeSplitScreen, mOnFinishListener); @@ -75,12 +83,17 @@ public class RecentsAnimationListenerSet implements RecentsAnimationListener { } @Override - public final void onAnimationCanceled() { + public final void onAnimationCanceled(boolean deferredWithScreenshot) { 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) { + MAIN_THREAD_EXECUTOR.getHandler().postDelayed(() -> mController.cleanupScreenshot(), + TRANSITION_DELAY); + } } private SwipeAnimationListener[] getListeners() { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java index 97bce5eb98..2ff2e3736e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java @@ -95,12 +95,7 @@ public class LauncherRecentsView extends RecentsView { @Override public void startHome() { - if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - takeScreenshotAndFinishRecentsAnimation(true, - () -> mActivity.getStateManager().goToState(NORMAL)); - } else { - mActivity.getStateManager().goToState(NORMAL); - } + mActivity.getStateManager().goToState(NORMAL); } @Override 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 3e0e8ae583..8f52f98aa7 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 @@ -94,7 +94,6 @@ import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener; import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskUtils; import com.android.quickstep.util.ClipAnimationHelper; -import com.android.quickstep.util.SwipeAnimationTargetSet; import com.android.quickstep.util.TaskViewDrawable; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -103,7 +102,6 @@ import com.android.systemui.shared.system.BackgroundExecutor; import com.android.systemui.shared.system.PackageManagerWrapper; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; import com.android.systemui.shared.system.TaskStackChangeListener; -import com.android.systemui.shared.system.WindowCallbacksCompat; import java.util.ArrayList; import java.util.function.Consumer; @@ -1577,50 +1575,4 @@ public abstract class RecentsView extends PagedView impl mRecentsAnimationWrapper.finish(toRecents, onFinishComplete); } - - public void takeScreenshotAndFinishRecentsAnimation(boolean toRecents, - Runnable onFinishComplete) { - if (mRecentsAnimationWrapper == null || getRunningTaskView() == null) { - if (onFinishComplete != null) { - onFinishComplete.run(); - } - return; - } - - SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController(); - if (controller != null) { - // Update the screenshot of the task - ThumbnailData taskSnapshot = controller.screenshotTask(mRunningTaskId); - TaskView taskView = updateThumbnail(mRunningTaskId, taskSnapshot); - if (taskView != null) { - taskView.setShowScreenshot(true); - // Defer finishing the animation until the next launcher frame with the - // new thumbnail - new WindowCallbacksCompat(taskView) { - - // The number of frames to defer until we actually finish the animation - private int mDeferFrameCount = 2; - - @Override - public void onPostDraw(Canvas canvas) { - if (mDeferFrameCount > 0) { - mDeferFrameCount--; - // Workaround, detach and reattach to invalidate the root node for - // another draw - detach(); - attach(); - taskView.invalidate(); - return; - } - - detach(); - mRecentsAnimationWrapper.finish(toRecents, () -> { - onFinishComplete.run(); - mRunningTaskId = -1; - }); - } - }.attach(); - } - } - } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java index 682152e9a2..d15a392635 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java @@ -208,13 +208,7 @@ public class TaskMenuView extends AbstractFloatingView { R.layout.task_view_menu_option, this, false); menuOption.setIconAndLabelFor( menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text)); - if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - menuOptionView.setOnClickListener( - view -> mTaskView.getRecentsView().takeScreenshotAndFinishRecentsAnimation(true, - () -> onClickListener.onClick(view))); - } else { - menuOptionView.setOnClickListener(onClickListener); - } + menuOptionView.setOnClickListener(onClickListener); mOptionLayout.addView(menuOptionView); } 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 e5b1a7988b..4256fa1fd0 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 @@ -259,11 +259,10 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { Handler resultCallbackHandler) { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { if (isRunningTask()) { - getRecentsView().finishRecentsAnimation(false, + getRecentsView().finishRecentsAnimation(false /* toRecents */, () -> resultCallbackHandler.post(() -> resultCallback.accept(true))); } else { - getRecentsView().takeScreenshotAndFinishRecentsAnimation(true, - () -> launchTaskInternal(animate, resultCallback, resultCallbackHandler)); + launchTaskInternal(animate, resultCallback, resultCallbackHandler); } } else { launchTaskInternal(animate, resultCallback, resultCallbackHandler); From e11eb47c4e7dab04646275d4524da98546a0fa85 Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Tue, 19 Mar 2019 15:05:21 -0700 Subject: [PATCH 49/68] Move overlay monitoring logic out of config monitor Bug: 126417894 Change-Id: I5b474a2a41bbd951fa0a99673fa8bf4342adade5 --- .../launcher3/InvariantDeviceProfile.java | 25 +++++++++++++++++++ .../android/launcher3/util/ConfigMonitor.java | 13 ---------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index 6582df2b78..f081303148 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -20,7 +20,10 @@ import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME; import static com.android.launcher3.Utilities.getDevicePrefs; import android.annotation.TargetApi; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; @@ -114,6 +117,7 @@ public class InvariantDeviceProfile { private final ArrayList mChangeListeners = new ArrayList<>(); private ConfigMonitor mConfigMonitor; + private OverlayMonitor mOverlayMonitor; @VisibleForTesting public InvariantDeviceProfile() {} @@ -131,6 +135,7 @@ public class InvariantDeviceProfile { defaultLayoutId = p.defaultLayoutId; demoModeLayoutId = p.demoModeLayoutId; mExtraAttrs = p.mExtraAttrs; + mOverlayMonitor = p.mOverlayMonitor; } @TargetApi(23) @@ -138,8 +143,12 @@ public class InvariantDeviceProfile { initGrid(context, Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)); mConfigMonitor = new ConfigMonitor(context, APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess); + mOverlayMonitor = new OverlayMonitor(context); } + /** + * This constructor should NOT have any monitors by design. + */ public InvariantDeviceProfile(Context context, String gridName) { String newName = initGrid(context, gridName); if (newName == null || !newName.equals(gridName)) { @@ -555,4 +564,20 @@ public class InvariantDeviceProfile { return this; } } + + private class OverlayMonitor extends BroadcastReceiver { + + private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED"; + + OverlayMonitor(Context context) { + IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED); + filter.addDataScheme("package"); + context.registerReceiver(this, filter); + } + + @Override + public void onReceive(Context context, Intent intent) { + onConfigChanged(context); + } + } } \ No newline at end of file diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java index 12280f82c4..12d35e9628 100644 --- a/src/com/android/launcher3/util/ConfigMonitor.java +++ b/src/com/android/launcher3/util/ConfigMonitor.java @@ -41,8 +41,6 @@ public class ConfigMonitor extends BroadcastReceiver implements DisplayListener private static final String TAG = "ConfigMonitor"; - private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED"; - private final Point mTmpPoint1 = new Point(); private final Point mTmpPoint2 = new Point(); @@ -78,11 +76,6 @@ public class ConfigMonitor extends BroadcastReceiver implements DisplayListener // Listen for configuration change mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED)); - // Listen for {@link OverlayManager} change - IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED); - filter.addDataScheme("package"); - mContext.registerReceiver(this, filter); - // Listen for display manager change mContext.getSystemService(DisplayManager.class) .registerDisplayListener(this, new Handler(UiThreadHelper.getBackgroundLooper())); @@ -91,12 +84,6 @@ public class ConfigMonitor extends BroadcastReceiver implements DisplayListener @Override public void onReceive(Context context, Intent intent) { Configuration config = context.getResources().getConfiguration(); - // TODO: when overlay manager service encodes more information to the Uri such as category - // of the overlay, only listen to the ones that are of interest to launcher. - if (intent != null && ACTION_OVERLAY_CHANGED.equals(intent.getAction())) { - Log.d(TAG, "Overlay changed."); - notifyChange(); - } if (mFontScale != config.fontScale || mDensity != config.densityDpi) { Log.d(TAG, "Configuration changed."); notifyChange(); From 0954d8ad49442f764321ca324b6e64e525103914 Mon Sep 17 00:00:00 2001 From: Matthew Ng Date: Tue, 19 Mar 2019 16:32:32 -0700 Subject: [PATCH 50/68] Recycle motion event after cancel delegate from assistant input consumer Related: ag/6737054 Test: manual Bug: 112934365 Change-Id: I7bf439fc58c986fa9972911a02a4035cfd867c34 --- .../src/com/android/quickstep/AssistantTouchConsumer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java index 54940529e0..c00b4dc70b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java @@ -160,6 +160,7 @@ public class AssistantTouchConsumer implements InputConsumer { MotionEvent event = MotionEvent.obtain(ev); event.setAction(MotionEvent.ACTION_CANCEL); mConsumerDelegate.onMotionEvent(event); + event.recycle(); } } else { mState = STATE_DELEGATE_ACTIVE; From 174a330d5dcea98d82503b9af70a848549f05a79 Mon Sep 17 00:00:00 2001 From: vadimt Date: Tue, 19 Mar 2019 19:13:38 -0700 Subject: [PATCH 51/68] Stop using magic constant 0.1 for switching from home to all apps Bug: 123904290 Change-Id: Ide4561adb2bb59c805cd9fd48f25440e68d91c7b --- .../quickstep/TestInformationProvider.java | 21 ++++++++++++++----- src/com/android/launcher3/TestProtocol.java | 2 ++ .../com/android/launcher3/tapl/Workspace.java | 7 ++++--- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/quickstep/src/com/android/quickstep/TestInformationProvider.java b/quickstep/src/com/android/quickstep/TestInformationProvider.java index 0c93dd6cf5..e57d3ec5cd 100644 --- a/quickstep/src/com/android/quickstep/TestInformationProvider.java +++ b/quickstep/src/com/android/quickstep/TestInformationProvider.java @@ -71,6 +71,9 @@ public class TestInformationProvider extends ContentProvider { final Context context = getContext(); final DeviceProfile deviceProfile = InvariantDeviceProfile.INSTANCE. get(context).getDeviceProfile(context); + final LauncherAppState launcherAppState = LauncherAppState.getInstanceNoCreate(); + final Launcher launcher = launcherAppState != null ? + (Launcher) launcherAppState.getModel().getCallback() : null; switch (method) { case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: { @@ -79,18 +82,15 @@ public class TestInformationProvider extends ContentProvider { response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight); break; } + case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: { final float swipeHeight = LayoutUtils.getShelfTrackingDistance(context, deviceProfile); response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight); break; } - case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: { - final LauncherAppState launcherAppState = - LauncherAppState.getInstanceNoCreate(); - if (launcherAppState == null) return null; - final Launcher launcher = (Launcher) launcherAppState.getModel().getCallback(); + case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: { if (launcher == null) return null; final float progress = LauncherState.OVERVIEW.getVerticalProgress(launcher) @@ -100,6 +100,17 @@ public class TestInformationProvider extends ContentProvider { response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance); break; } + + case TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT: { + if (launcher == null) return null; + + final float progress = LauncherState.NORMAL.getVerticalProgress(launcher) + - LauncherState.ALL_APPS.getVerticalProgress(launcher); + final float distance = + launcher.getAllAppsController().getShiftRange() * progress; + response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance); + break; + } } return response; } diff --git a/src/com/android/launcher3/TestProtocol.java b/src/com/android/launcher3/TestProtocol.java index a4e4227153..4eb3627566 100644 --- a/src/com/android/launcher3/TestProtocol.java +++ b/src/com/android/launcher3/TestProtocol.java @@ -38,4 +38,6 @@ public final class TestProtocol { "background-to-overview-swipe-height"; public static final String REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT = "all-apps-to-overview-swipe-height"; + public static final String REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT = + "home-to-all-apps-swipe-height"; } diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java index 819e10d089..e3ef74ab73 100644 --- a/tests/tapl/com/android/launcher3/tapl/Workspace.java +++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java @@ -51,15 +51,16 @@ public final class Workspace extends Home { @NonNull public AllApps switchToAllApps() { verifyActiveContainer(); - // Swipe from the hotseat to near the top, e.g. 10% of the screen. final UiObject2 hotseat = mHotseat; final Point start = hotseat.getVisibleCenter(); - final int endY = (int) (mLauncher.getDevice().getDisplayHeight() * 0.1f); + final int swipeHeight = mLauncher.getTestInfo( + TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT). + getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); mLauncher.swipe( start.x, start.y, start.x, - endY, + start.y - swipeHeight - mLauncher.getTouchSlop(), ALL_APPS_STATE_ORDINAL ); From 272ff626cb87f89ffa84e21b27ef29dd811649e5 Mon Sep 17 00:00:00 2001 From: Chilun Date: Wed, 20 Mar 2019 15:04:38 +0800 Subject: [PATCH 52/68] Hide keyboard while the home button get pressed For multi-client IME, launcher need to call hideSoftInputFromWindow() by using the InputMethodManager instance that mapped the presented display while its home button get pressed. Launcher 3 has already handled this part, but the secondary launcher has not yet. Bug: 120114697 Test: Manual verified that multi-client IME can be closed correctly. Change-Id: I8959c3a470580452bfb95dbd4b4ce9268589000f --- .../launcher3/SecondaryDisplayLauncher.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java b/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java index 12fa46bf44..0a2f18f109 100644 --- a/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java +++ b/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java @@ -26,12 +26,11 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.os.Bundle; -import com.google.android.material.circularreveal.cardview.CircularRevealCardView; -import com.google.android.material.floatingactionbutton.FloatingActionButton; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewAnimationUtils; +import android.view.inputmethod.InputMethodManager; import android.widget.GridView; import android.widget.ImageButton; import android.widget.PopupMenu; @@ -41,6 +40,9 @@ import androidx.fragment.app.FragmentManager; import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory; +import com.google.android.material.circularreveal.cardview.CircularRevealCardView; +import com.google.android.material.floatingactionbutton.FloatingActionButton; + import java.util.HashSet; import java.util.Set; @@ -141,6 +143,16 @@ public class SecondaryDisplayLauncher extends FragmentActivity implements AppPic public void onNewIntent(Intent intent) { super.onNewIntent(intent); + + if (Intent.ACTION_MAIN.equals(intent.getAction())) { + // Hide keyboard. + final View v = getWindow().peekDecorView(); + if (v != null && v.getWindowToken() != null) { + getSystemService(InputMethodManager.class).hideSoftInputFromWindow( + v.getWindowToken(), 0); + } + } + // A new intent will bring the launcher to top. Hide the app drawer to reset the state. showAppDrawer(false); } From cded01bea9fe88962612499d6331c5739357ffeb Mon Sep 17 00:00:00 2001 From: Tony Date: Wed, 20 Mar 2019 12:45:54 -0500 Subject: [PATCH 53/68] Fix SpringObjectAnimator not sending onAnimationEnd() We only send onAnimationEnd() if both object animator and spring animator have ended. However, we also initialized mSpringEnded = false to begin with, which meant we would never send onAnimationEnd() if the spring animator was never started. Now we treat the animators as ended until they are started. Example test: 0: Enable SWIPE_HOME and QUICKSTEP_SPRINGS flags 1: Swipe up and hold on home to go to overview 2: Tap outside overview to go home 3. Swipe up to all apps 4. Swipe down Expected: animates to home, not overview. Before this change, however, it went to overview because the state manager never got onAnimationEnd() and therefore never set the last stable state to NORMAL instead of OVERVIEW. Change-Id: I7add42eedbc15b3a6a08ac69570da4edd57b5417 --- .../android/launcher3/anim/SpringObjectAnimator.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java index cc70e32edb..f74590bba3 100644 --- a/src/com/android/launcher3/anim/SpringObjectAnimator.java +++ b/src/com/android/launcher3/anim/SpringObjectAnimator.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.anim; +import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -32,8 +34,6 @@ import androidx.dynamicanimation.animation.FloatPropertyCompat; import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; -import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; - /** * This animator allows for an object's property to be be controlled by an {@link ObjectAnimator} or * a {@link SpringAnimation}. It extends ValueAnimator so it can be used in an AnimatorSet. @@ -51,9 +51,9 @@ public class SpringObjectAnimator extends ValueAnim private SpringProperty mProperty; private ArrayList mListeners; - private boolean mSpringEnded = false; - private boolean mAnimatorEnded = false; - private boolean mEnded = false; + private boolean mSpringEnded = true; + private boolean mAnimatorEnded = true; + private boolean mEnded = true; private static final FloatPropertyCompat sFloatProperty = new FloatPropertyCompat("springObjectAnimator") { From 6f647c7e6bc7c7e2aa5d367b954ffa41b568a7bc Mon Sep 17 00:00:00 2001 From: vadimt Date: Wed, 20 Mar 2019 10:50:44 -0700 Subject: [PATCH 54/68] Finding icon in All Apps makes sure the icon is not covered by navbar Change-Id: I24f8bd9871f7cf8f1313f7eb7a11f1b3f71a099b --- tests/tapl/com/android/launcher3/tapl/AllApps.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java index 1353a23bde..122151e9d2 100644 --- a/tests/tapl/com/android/launcher3/tapl/AllApps.java +++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java @@ -44,6 +44,13 @@ public class AllApps extends LauncherInstrumentation.VisibleContainer { return LauncherInstrumentation.ContainerType.ALL_APPS; } + private boolean hasClickableIcon(UiObject2 allAppsContainer, BySelector appIconSelector) { + final UiObject2 icon = allAppsContainer.findObject(appIconSelector); + if (icon == null) return false; + final UiObject2 navBar = mLauncher.waitForSystemUiObject("navigation_bar_frame"); + return icon.getVisibleBounds().bottom < navBar.getVisibleBounds().top; + } + /** * Finds an icon. Fails if the icon doesn't exist. Scrolls the app list when needed to make * sure the icon is visible. @@ -55,10 +62,10 @@ public class AllApps extends LauncherInstrumentation.VisibleContainer { public AppIcon getAppIcon(String appName) { final UiObject2 allAppsContainer = verifyActiveContainer(); final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher); - if (!allAppsContainer.hasObject(appIconSelector)) { + if (!hasClickableIcon(allAppsContainer, appIconSelector)) { scrollBackToBeginning(); int attempts = 0; - while (!allAppsContainer.hasObject(appIconSelector) && + while (!hasClickableIcon(allAppsContainer, appIconSelector) && allAppsContainer.scroll(Direction.DOWN, 0.8f)) { LauncherInstrumentation.assertTrue( "Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS, From e73b0ec69d287947f87ae899ec3db4c0fe18e3e5 Mon Sep 17 00:00:00 2001 From: Vadim Tryshev Date: Wed, 20 Mar 2019 18:07:11 +0000 Subject: [PATCH 55/68] Revert "Using startActivityForResult to open DWB properties" This reverts commit b480fe45ce7c20a3f4652b3970f3272888f94772. Reason for revert: Conflicts with gestural navigation Change-Id: I109fb855829b68d265458f9b3833482df2b59095 --- .../src/com/android/quickstep/views/DigitalWellBeingToast.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java index cf42a36ab7..19e9cb4ff6 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java @@ -198,7 +198,7 @@ public final class DigitalWellBeingToast extends LinearLayout { final ActivityOptions options = ActivityOptions.makeScaleUpAnimation( this, 0, 0, getWidth(), getHeight()); - launcher.startActivityForResult(intent, 0, options.toBundle()); + launcher.startActivity(intent, options.toBundle()); launcher.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP, LauncherLogProto.ControlType.APP_USAGE_SETTINGS, this); } catch (ActivityNotFoundException e) { From bfc9dde03eef8756c439f62eee690b0fcf396c5c Mon Sep 17 00:00:00 2001 From: Tony Date: Fri, 15 Mar 2019 16:55:53 -0500 Subject: [PATCH 56/68] Replace translationYFactor with just translationY Now that we don't have quick scrub, the only state that has a non-zero translation is all apps, which just uses that to have a slight parallax. This is much simpler to define in terms of pixels like other states do. Change-Id: I108c8505d85591399256b3475f7566ff51e2c5ad --- .../launcher3/uioverrides/OverviewState.java | 2 +- .../RecentsViewStateController.java | 10 +---- .../quickstep/views/IconRecentsView.java | 24 ------------ .../uioverrides/BackgroundAppState.java | 2 +- .../launcher3/uioverrides/OverviewState.java | 2 +- .../RecentsViewStateController.java | 10 +---- .../LauncherActivityControllerHelper.java | 4 +- .../quickstep/views/LauncherRecentsView.java | 37 ++----------------- .../launcher3/uioverrides/AllAppsState.java | 5 ++- .../BaseRecentsViewStateController.java | 23 ++++-------- src/com/android/launcher3/LauncherState.java | 8 +--- 11 files changed, 23 insertions(+), 104 deletions(-) diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java index 5382607a14..cec12a8d18 100644 --- a/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java +++ b/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java @@ -43,7 +43,7 @@ public class OverviewState extends LauncherState { } @Override - public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) { + public float[] getOverviewScaleAndTranslationY(Launcher launcher) { return new float[] {1f, 0f}; } diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java index 784af7d11a..0b12ab052e 100644 --- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java +++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java @@ -16,16 +16,15 @@ package com.android.launcher3.uioverrides; import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA; -import static com.android.quickstep.views.IconRecentsView.TRANSLATION_Y_FACTOR; import android.util.FloatProperty; -import androidx.annotation.NonNull; - import com.android.launcher3.Launcher; import com.android.launcher3.LauncherRecentsToActivityHelper; import com.android.quickstep.views.IconRecentsView; +import androidx.annotation.NonNull; + /** * State handler for Go's {@link IconRecentsView}. */ @@ -38,11 +37,6 @@ public final class RecentsViewStateController extends new LauncherRecentsToActivityHelper(launcher)); } - @Override - FloatProperty getTranslationYFactorProperty() { - return TRANSLATION_Y_FACTOR; - } - @Override FloatProperty getContentAlphaProperty() { return CONTENT_ALPHA; diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index 3fdaefe156..504f640069 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -46,20 +46,6 @@ import com.android.quickstep.TaskSwipeCallback; */ public final class IconRecentsView extends FrameLayout { - public static final FloatProperty TRANSLATION_Y_FACTOR = - new FloatProperty("translationYFactor") { - - @Override - public void setValue(IconRecentsView view, float v) { - view.setTranslationYFactor(v); - } - - @Override - public Float get(IconRecentsView view) { - return view.mTranslationYFactor; - } - }; - public static final FloatProperty CONTENT_ALPHA = new FloatProperty("contentAlpha") { @Override @@ -91,7 +77,6 @@ public final class IconRecentsView extends FrameLayout { private final TaskInputController mTaskInputController; private RecentsToActivityHelper mActivityHelper; - private float mTranslationYFactor; private RecyclerView mTaskRecyclerView; private View mEmptyView; @@ -170,15 +155,6 @@ public final class IconRecentsView extends FrameLayout { return view.getThumbnailView(); } - public void setTranslationYFactor(float translationFactor) { - mTranslationYFactor = translationFactor; - setTranslationY(computeTranslationYForFactor(mTranslationYFactor)); - } - - private float computeTranslationYForFactor(float translationYFactor) { - return translationYFactor * (getPaddingBottom() - getPaddingTop()); - } - /** * Update the content view so that the appropriate view is shown based off the current list * of tasks. diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java index fdb80da387..f7127537a5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java @@ -52,7 +52,7 @@ public class BackgroundAppState extends OverviewState { } @Override - public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) { + public float[] getOverviewScaleAndTranslationY(Launcher launcher) { // Initialize the recents view scale to what it would be when starting swipe up RecentsView recentsView = launcher.getOverviewPanel(); recentsView.getTaskSize(sTempRect); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java index 79e127a8d7..2360eebc46 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java @@ -69,7 +69,7 @@ public class OverviewState extends LauncherState { } @Override - public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) { + public float[] getOverviewScaleAndTranslationY(Launcher launcher) { return new float[] {1f, 0f}; } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java index 0b3bd6c792..0d5574fa91 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java @@ -15,7 +15,6 @@ */ package com.android.launcher3.uioverrides; -import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR; import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA; import android.animation.ValueAnimator; @@ -23,8 +22,6 @@ import android.annotation.TargetApi; import android.os.Build; import android.util.FloatProperty; -import androidx.annotation.NonNull; - import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager.AnimationConfig; @@ -32,6 +29,8 @@ import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.quickstep.views.LauncherRecentsView; import com.android.quickstep.views.RecentsView; +import androidx.annotation.NonNull; + /** * State handler for handling UI changes for {@link LauncherRecentsView}. In addition to managing * the basic view properties, this class also manages changes in the task visuals. @@ -79,11 +78,6 @@ public final class RecentsViewStateController extends } } - @Override - FloatProperty getTranslationYFactorProperty() { - return TRANSLATION_Y_FACTOR; - } - @Override FloatProperty getContentAlphaProperty() { return CONTENT_ALPHA; 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 e95e2a0de5..20a22e98a1 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -56,9 +56,7 @@ import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.SpringObjectAnimator; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.userevent.nano.LauncherLogProto; -import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.util.ClipAnimationHelper; import com.android.quickstep.util.LayoutUtils; @@ -308,7 +306,7 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe // starting to line up the side pages during swipe up) float prevRvScale = recentsView.getScaleX(); float prevRvTransY = recentsView.getTranslationY(); - float targetRvScale = endState.getOverviewScaleAndTranslationYFactor(launcher)[0]; + float targetRvScale = endState.getOverviewScaleAndTranslationY(launcher)[0]; SCALE_PROPERTY.set(recentsView, targetRvScale); recentsView.setTranslationY(0); ClipAnimationHelper clipHelper = new ClipAnimationHelper(launcher); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java index 97bce5eb98..8f2a2d7258 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java @@ -56,27 +56,6 @@ import com.android.quickstep.util.LayoutUtils; @TargetApi(Build.VERSION_CODES.O) public class LauncherRecentsView extends RecentsView { - public static final FloatProperty TRANSLATION_Y_FACTOR = - new FloatProperty("translationYFactor") { - - @Override - public void setValue(LauncherRecentsView view, float v) { - view.setTranslationYFactor(v); - } - - @Override - public Float get(LauncherRecentsView view) { - return view.mTranslationYFactor; - } - }; - - /** - * A ratio representing the view's relative placement within its padded space. For example, 0 - * is top aligned and 0.5 is centered vertically. - */ - @ViewDebug.ExportedProperty(category = "launcher") - private float mTranslationYFactor; - private final TransformParams mTransformParams = new TransformParams(); private ChipsContainer mChipsContainer; @@ -103,12 +82,6 @@ public class LauncherRecentsView extends RecentsView { } } - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - setTranslationYFactor(mTranslationYFactor); - } - @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); @@ -117,9 +90,9 @@ public class LauncherRecentsView extends RecentsView { params.bottomMargin = mActivity.getDeviceProfile().chipHintBottomMarginPx; } - public void setTranslationYFactor(float translationFactor) { - mTranslationYFactor = translationFactor; - setTranslationY(computeTranslationYForFactor(mTranslationYFactor)); + @Override + public void setTranslationY(float translationY) { + super.setTranslationY(translationY); if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { LauncherState state = mActivity.getStateManager().getState(); if (state == OVERVIEW || state == ALL_APPS) { @@ -128,10 +101,6 @@ public class LauncherRecentsView extends RecentsView { } } - public float computeTranslationYForFactor(float translationYFactor) { - return translationYFactor * (getPaddingBottom() - getPaddingTop()); - } - public void setHintVisibility(float v) { if (mChipsContainer != null && ENABLE_HINTS_IN_OVERVIEW.get()) { mChipsContainer.setHintVisibility(v); diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java index 1eaa8bc92c..5ae562e8bd 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java @@ -78,8 +78,9 @@ public class AllAppsState extends LauncherState { } @Override - public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) { - return new float[] {0.9f, -0.2f}; + public float[] getOverviewScaleAndTranslationY(Launcher launcher) { + float slightParallax = -launcher.getDeviceProfile().allAppsCellHeightPx * 0.3f; + return new float[] {0.9f, slightParallax}; } @Override diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java index df9dbe4119..e74d84dc28 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java @@ -26,8 +26,6 @@ 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.LauncherStateManager.AnimationConfig; @@ -35,6 +33,8 @@ import com.android.launcher3.LauncherStateManager.StateHandler; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.PropertySetter; +import androidx.annotation.NonNull; + /** * State handler for recents view. Manages UI changes and animations for recents view based off the * current {@link LauncherState}. @@ -53,9 +53,9 @@ public abstract class BaseRecentsViewStateController @Override public void setState(@NonNull LauncherState state) { - float[] scaleTranslationYFactor = state.getOverviewScaleAndTranslationYFactor(mLauncher); - SCALE_PROPERTY.set(mRecentsView, scaleTranslationYFactor[0]); - getTranslationYFactorProperty().set(mRecentsView, scaleTranslationYFactor[1]); + float[] scaleTranslationY = state.getOverviewScaleAndTranslationY(mLauncher); + SCALE_PROPERTY.set(mRecentsView, scaleTranslationY[0]); + mRecentsView.setTranslationY(scaleTranslationY[1]); getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0); } @@ -79,11 +79,11 @@ public abstract class BaseRecentsViewStateController void setStateWithAnimationInternal(@NonNull final LauncherState toState, @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) { PropertySetter setter = config.getPropertySetter(builder); - float[] scaleTranslationYFactor = toState.getOverviewScaleAndTranslationYFactor(mLauncher); + float[] scaleTranslationY = toState.getOverviewScaleAndTranslationY(mLauncher); Interpolator scaleAndTransYInterpolator = getScaleAndTransYInterpolator(toState, builder); - setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleTranslationYFactor[0], + setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleTranslationY[0], scaleAndTransYInterpolator); - setter.setFloat(mRecentsView, getTranslationYFactorProperty(), scaleTranslationYFactor[1], + setter.setFloat(mRecentsView, View.TRANSLATION_Y, scaleTranslationY[1], scaleAndTransYInterpolator); setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0, builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT)); @@ -101,13 +101,6 @@ public abstract class BaseRecentsViewStateController return builder.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR); } - /** - * Get property for translation Y factor for the recents view. - * - * @return the float property for the recents view - */ - abstract FloatProperty getTranslationYFactorProperty(); - /** * Get property for content alpha for the recents view. * diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java index cee1c26ef4..875288a55c 100644 --- a/src/com/android/launcher3/LauncherState.java +++ b/src/com/android/launcher3/LauncherState.java @@ -192,13 +192,7 @@ public class LauncherState { return getWorkspaceScaleAndTranslation(launcher); } - /** - * Returns 2 floats designating how to transition overview: - * scale for the current and adjacent pages - * translationY factor where 0 is top aligned and 0.5 is centered vertically - */ - public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) { - // TODO: Simplify to use a constant value instead of a factor. + public float[] getOverviewScaleAndTranslationY(Launcher launcher) { return new float[] {1.1f, 0f}; } From 72114efef69f5449b784cd45c7e3b66f8f53290b Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 19 Mar 2019 10:25:27 -0700 Subject: [PATCH 57/68] Add recents launch animation to Recents Go We use the basic clip reveal animation to animate from the task thumbnail view to the opening app. Bug: 114136250 Test: Tap task from recents Change-Id: Id582385fc022c75ee322beebdc6cf7a0f209e720 --- .../GoLauncherAppTransitionManagerImpl.java | 3 ++- .../com/android/quickstep/RecentsActivity.java | 2 +- .../android/quickstep/TaskInputController.java | 15 +++++++++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java index 96b4ae569e..d189c504cd 100644 --- a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java +++ b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java @@ -31,7 +31,8 @@ public final class GoLauncherAppTransitionManagerImpl extends QuickstepAppTransi @Override protected void composeRecentsLaunchAnimator(AnimatorSet anim, View v, RemoteAnimationTargetCompat[] targets, boolean launcherClosing) { - //TODO: Implement this based off IconRecentsView + // Stubbed. Recents launch animation will come from the recents view itself and will not + // use remote animations. } @Override diff --git a/go/quickstep/src/com/android/quickstep/RecentsActivity.java b/go/quickstep/src/com/android/quickstep/RecentsActivity.java index c814a71f08..447e7e7235 100644 --- a/go/quickstep/src/com/android/quickstep/RecentsActivity.java +++ b/go/quickstep/src/com/android/quickstep/RecentsActivity.java @@ -61,7 +61,7 @@ public final class RecentsActivity extends BaseRecentsActivity { @Override public ActivityOptions getActivityLaunchOptions(View v) { - //TODO: Hook into recents launch animation + // Stubbed. Recents launch animation will come from the recents view itself. return null; } diff --git a/go/quickstep/src/com/android/quickstep/TaskInputController.java b/go/quickstep/src/com/android/quickstep/TaskInputController.java index d97ac8da90..8433007414 100644 --- a/go/quickstep/src/com/android/quickstep/TaskInputController.java +++ b/go/quickstep/src/com/android/quickstep/TaskInputController.java @@ -15,6 +15,10 @@ */ package com.android.quickstep; +import android.app.ActivityOptions; +import android.view.View; + +import com.android.quickstep.views.TaskItemView; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -37,9 +41,16 @@ public final class TaskInputController { * @param viewHolder the task view holder that has been tapped */ public void onTaskClicked(TaskHolder viewHolder) { - // TODO: Add app launch animation as part of the launch options here. + TaskItemView itemView = (TaskItemView) (viewHolder.itemView); + View v = itemView.getThumbnailView(); + int left = 0; + int top = 0; + int width = v.getMeasuredWidth(); + int height = v.getMeasuredHeight(); + + ActivityOptions opts = ActivityOptions.makeClipRevealAnimation(v, left, top, width, height); ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(viewHolder.getTask().key, - null /* options */, null /* resultCallback */, null /* resultCallbackHandler */); + opts, null /* resultCallback */, null /* resultCallbackHandler */); } public void onTaskSwiped(TaskHolder viewHolder) { From e790b217e122ed7e5b3cf300a4ef8285097c6665 Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Tue, 12 Mar 2019 23:36:47 -0700 Subject: [PATCH 58/68] Properly clean up screenshot of recents animation upon cancelation Bug: 122593881 Test: Manual Change-Id: Ia9ce1ede08309a0898c622bcd07a9e076443d98a (cherry picked from commit 2bd3a0225b373c21774e7f0806ff49853d410042) --- .../quickstep/RecentsAnimationWrapper.java | 6 +++ .../WindowTransformSwipeHandler.java | 1 + .../util/RecentsAnimationListenerSet.java | 15 +++++- .../quickstep/views/LauncherRecentsView.java | 7 +-- .../android/quickstep/views/RecentsView.java | 48 ------------------- .../android/quickstep/views/TaskMenuView.java | 8 +--- .../com/android/quickstep/views/TaskView.java | 5 +- 7 files changed, 25 insertions(+), 65 deletions(-) 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 f0bc223ded..0924f38dcf 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java @@ -173,6 +173,12 @@ public class RecentsAnimationWrapper { return true; } + public void setCancelWithDeferredScreenshot(boolean deferredWithScreenshot) { + if (targetSet != null) { + targetSet.controller.setCancelWithDeferredScreenshot(deferredWithScreenshot); + } + } + public SwipeAnimationTargetSet getController() { return targetSet; } 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 eb1e7b4077..e8d4c19a73 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -1136,6 +1136,7 @@ public class WindowTransformSwipeHandler mLauncherTransitionController = null; } mActivityControlHelper.onSwipeUpComplete(mActivity); + mRecentsAnimationWrapper.setCancelWithDeferredScreenshot(true); // Animate the first icon. mRecentsView.animateUpRunningTaskIconScale(mLiveTileOverlay.cancelIconAnimation()); 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 62f2183b50..94e704a716 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 @@ -38,9 +38,16 @@ 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 + // after app transition is finished. This delay is introduced to cover the app transition + // period of time. + private final int TRANSITION_DELAY = 100; + private final Set mListeners = new ArraySet<>(); private final boolean mShouldMinimizeSplitScreen; private final Consumer mOnFinishListener; + private RecentsAnimationControllerCompat mController; public RecentsAnimationListenerSet(boolean shouldMinimizeSplitScreen, Consumer onFinishListener) { @@ -64,6 +71,7 @@ public class RecentsAnimationListenerSet implements RecentsAnimationListener { public final void onAnimationStart(RecentsAnimationControllerCompat controller, RemoteAnimationTargetCompat[] targets, Rect homeContentInsets, Rect minimizedHomeBounds) { + mController = controller; SwipeAnimationTargetSet targetSet = new SwipeAnimationTargetSet(controller, targets, homeContentInsets, minimizedHomeBounds, mShouldMinimizeSplitScreen, mOnFinishListener); @@ -75,12 +83,17 @@ public class RecentsAnimationListenerSet implements RecentsAnimationListener { } @Override - public final void onAnimationCanceled() { + public final void onAnimationCanceled(boolean deferredWithScreenshot) { 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) { + MAIN_THREAD_EXECUTOR.getHandler().postDelayed(() -> mController.cleanupScreenshot(), + TRANSITION_DELAY); + } } private SwipeAnimationListener[] getListeners() { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java index 8f2a2d7258..8da1d2b7da 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java @@ -74,12 +74,7 @@ public class LauncherRecentsView extends RecentsView { @Override public void startHome() { - if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - takeScreenshotAndFinishRecentsAnimation(true, - () -> mActivity.getStateManager().goToState(NORMAL)); - } else { - mActivity.getStateManager().goToState(NORMAL); - } + mActivity.getStateManager().goToState(NORMAL); } @Override 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 37febf9a09..a02df62f3e 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 @@ -96,7 +96,6 @@ import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener; import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskUtils; import com.android.quickstep.util.ClipAnimationHelper; -import com.android.quickstep.util.SwipeAnimationTargetSet; import com.android.quickstep.util.TaskViewDrawable; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -106,7 +105,6 @@ import com.android.systemui.shared.system.LauncherEventUtil; import com.android.systemui.shared.system.PackageManagerWrapper; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; import com.android.systemui.shared.system.TaskStackChangeListener; -import com.android.systemui.shared.system.WindowCallbacksCompat; import java.util.ArrayList; import java.util.function.Consumer; @@ -1608,50 +1606,4 @@ public abstract class RecentsView extends PagedView impl mRecentsAnimationWrapper.finish(toRecents, onFinishComplete); } - - public void takeScreenshotAndFinishRecentsAnimation(boolean toRecents, - Runnable onFinishComplete) { - if (mRecentsAnimationWrapper == null || getRunningTaskView() == null) { - if (onFinishComplete != null) { - onFinishComplete.run(); - } - return; - } - - SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController(); - if (controller != null) { - // Update the screenshot of the task - ThumbnailData taskSnapshot = controller.screenshotTask(mRunningTaskId); - TaskView taskView = updateThumbnail(mRunningTaskId, taskSnapshot); - if (taskView != null) { - taskView.setShowScreenshot(true); - // Defer finishing the animation until the next launcher frame with the - // new thumbnail - new WindowCallbacksCompat(taskView) { - - // The number of frames to defer until we actually finish the animation - private int mDeferFrameCount = 2; - - @Override - public void onPostDraw(Canvas canvas) { - if (mDeferFrameCount > 0) { - mDeferFrameCount--; - // Workaround, detach and reattach to invalidate the root node for - // another draw - detach(); - attach(); - taskView.invalidate(); - return; - } - - detach(); - mRecentsAnimationWrapper.finish(toRecents, () -> { - onFinishComplete.run(); - mRunningTaskId = -1; - }); - } - }.attach(); - } - } - } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java index 682152e9a2..d15a392635 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java @@ -208,13 +208,7 @@ public class TaskMenuView extends AbstractFloatingView { R.layout.task_view_menu_option, this, false); menuOption.setIconAndLabelFor( menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text)); - if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - menuOptionView.setOnClickListener( - view -> mTaskView.getRecentsView().takeScreenshotAndFinishRecentsAnimation(true, - () -> onClickListener.onClick(view))); - } else { - menuOptionView.setOnClickListener(onClickListener); - } + menuOptionView.setOnClickListener(onClickListener); mOptionLayout.addView(menuOptionView); } 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 98495db9ae..38aaac5596 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 @@ -261,11 +261,10 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { Handler resultCallbackHandler) { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { if (isRunningTask()) { - getRecentsView().finishRecentsAnimation(false, + getRecentsView().finishRecentsAnimation(false /* toRecents */, () -> resultCallbackHandler.post(() -> resultCallback.accept(true))); } else { - getRecentsView().takeScreenshotAndFinishRecentsAnimation(true, - () -> launchTaskInternal(animate, resultCallback, resultCallbackHandler)); + launchTaskInternal(animate, resultCallback, resultCallbackHandler); } } else { launchTaskInternal(animate, resultCallback, resultCallbackHandler); From 8bba01874a8ee4663b85c4a540745377468e740b Mon Sep 17 00:00:00 2001 From: vadimt Date: Wed, 13 Mar 2019 18:37:41 -0700 Subject: [PATCH 59/68] Initial implementation for TAPL support for gestural navigation It cuts some corners, flaky and has magic constants, which will be addressed later. Change-Id: I0acc75338a3c6d0ead3afda01e25ba43eb095913 --- .../com/android/launcher3/tapl/AppIcon.java | 7 +- .../android/launcher3/tapl/Background.java | 54 +++++-- .../android/launcher3/tapl/BaseOverview.java | 8 +- .../tapl/LauncherInstrumentation.java | 137 ++++++++++++++---- .../com/android/launcher3/tapl/Workspace.java | 4 + 5 files changed, 167 insertions(+), 43 deletions(-) diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java index d39a38ea1d..fbeb3a2d01 100644 --- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java +++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java @@ -17,6 +17,7 @@ package com.android.launcher3.tapl; import android.graphics.Point; +import android.os.SystemClock; import android.view.MotionEvent; import android.widget.TextView; @@ -41,10 +42,12 @@ public final class AppIcon extends Launchable { */ public AppIconMenu openMenu() { final Point iconCenter = mObject.getVisibleCenter(); - mLauncher.sendPointer(MotionEvent.ACTION_DOWN, iconCenter); + final long downTime = SystemClock.uptimeMillis(); + mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, iconCenter); final UiObject2 deepShortcutsContainer = mLauncher.waitForLauncherObject( "deep_shortcuts_container"); - mLauncher.sendPointer(MotionEvent.ACTION_UP, iconCenter); + mLauncher.sendPointer( + downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, iconCenter); return new AppIconMenu(mLauncher, deepShortcutsContainer); } } diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java index 2cde8ecd24..ef509edebe 100644 --- a/tests/tapl/com/android/launcher3/tapl/Background.java +++ b/tests/tapl/com/android/launcher3/tapl/Background.java @@ -22,6 +22,10 @@ import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName; import static org.junit.Assert.assertTrue; +import android.graphics.Point; +import android.os.SystemClock; +import android.view.MotionEvent; + import androidx.annotation.NonNull; import androidx.test.uiautomator.By; import androidx.test.uiautomator.Until; @@ -33,6 +37,8 @@ import com.android.launcher3.TestProtocol; * indicate Launcher as long as Launcher is not in Overview state. */ public class Background extends LauncherInstrumentation.VisibleContainer { + private static final int ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION = 500; + private static final int ZERO_BUTTON_SWIPE_UP_HOLD_DURATION = 400; Background(LauncherInstrumentation launcher) { super(launcher); @@ -59,19 +65,41 @@ public class Background extends LauncherInstrumentation.VisibleContainer { } protected void goToOverviewUnchecked(int expectedState) { - if (mLauncher.isSwipeUpEnabled()) { - final int centerX = mLauncher.getDevice().getDisplayWidth() / 2; - final int startY = getSwipeStartY(); - final int swipeHeight = mLauncher.getTestInfo( - getSwipeHeightRequestName()). - getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); + switch (mLauncher.getNavigationModel()) { + case ZERO_BUTTON: { + final int centerX = mLauncher.getDevice().getDisplayWidth() / 2; + final int startY = getSwipeStartY(); + final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()). + getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); + final Point start = new Point(centerX, startY); + final Point end = + new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop()); - mLauncher.swipe( - centerX, startY, centerX, - startY - swipeHeight - mLauncher.getTouchSlop(), - expectedState); - } else { - mLauncher.waitForSystemUiObject("recent_apps").click(); + final long downTime = SystemClock.uptimeMillis(); + mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start); + mLauncher.movePointer(downTime, ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION, start, end); + LauncherInstrumentation.sleep(ZERO_BUTTON_SWIPE_UP_HOLD_DURATION); + mLauncher.sendPointer( + downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, end); + break; + } + + case TWO_BUTTON: { + final int centerX = mLauncher.getDevice().getDisplayWidth() / 2; + final int startY = getSwipeStartY(); + final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()). + getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); + + mLauncher.swipe( + centerX, startY, centerX, + startY - swipeHeight - mLauncher.getTouchSlop(), + expectedState); + break; + } + + case THREE_BUTTON: + mLauncher.waitForSystemUiObject("recent_apps").click(); + break; } } @@ -80,6 +108,6 @@ public class Background extends LauncherInstrumentation.VisibleContainer { } protected int getSwipeStartY() { - return mLauncher.waitForSystemUiObject("home").getVisibleBounds().centerY(); + return mLauncher.waitForSystemUiObject("navigation_bar_frame").getVisibleBounds().centerY(); } } diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java index 6e92dad3b4..8cf1262af4 100644 --- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java +++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java @@ -44,8 +44,10 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { * Flings forward (left) and waits the fling's end. */ public void flingForward() { - final UiObject2 overview = verifyActiveContainer(); LauncherInstrumentation.log("Overview.flingForward before fling"); + final UiObject2 overview = verifyActiveContainer(); + final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1; + overview.setGestureMargins(margin, 0, 0, 0); overview.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity())); mLauncher.waitForIdle(); verifyActiveContainer(); @@ -71,8 +73,10 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { * Flings backward (right) and waits the fling's end. */ public void flingBackward() { - final UiObject2 overview = verifyActiveContainer(); LauncherInstrumentation.log("Overview.flingBackward before fling"); + final UiObject2 overview = verifyActiveContainer(); + final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1; + overview.setGestureMargins(0, 0, margin, 0); overview.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity())); mLauncher.waitForIdle(); verifyActiveContainer(); diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index e3850ff0fd..f44022baac 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -16,6 +16,7 @@ package com.android.launcher3.tapl; +import static com.android.launcher3.TestProtocol.BACKGROUND_APP_STATE_ORDINAL; import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME; import android.app.ActivityManager; @@ -30,14 +31,17 @@ import android.os.Parcelable; import android.os.SystemClock; import android.provider.Settings; import android.util.Log; +import android.view.InputDevice; import android.view.MotionEvent; import android.view.Surface; import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.test.uiautomator.By; import androidx.test.uiautomator.BySelector; +import androidx.test.uiautomator.Configurator; import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.UiObject2; import androidx.test.uiautomator.Until; @@ -59,6 +63,7 @@ import java.util.concurrent.TimeoutException; public final class LauncherInstrumentation { private static final String TAG = "Tapl"; + private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 20; // Types for launcher containers that the user is interacting with. "Background" is a // pseudo-container corresponding to inactive launcher covered by another app. @@ -66,6 +71,8 @@ public final class LauncherInstrumentation { WORKSPACE, ALL_APPS, OVERVIEW, WIDGETS, BACKGROUND, BASE_OVERVIEW } + public enum NavigationModel {ZERO_BUTTON, TWO_BUTTON, THREE_BUTTON} + // Base class for launcher containers. static abstract class VisibleContainer { protected final LauncherInstrumentation mLauncher; @@ -150,7 +157,11 @@ public final class LauncherInstrumentation { sActiveContainer = new WeakReference<>(container); } - public boolean isSwipeUpEnabled() { + public NavigationModel getNavigationModel() { + return isSwipeUpEnabled() ? NavigationModel.TWO_BUTTON : NavigationModel.THREE_BUTTON; + } + + private boolean isSwipeUpEnabled() { final boolean swipeUpEnabledDefaultValue = SwipeUpSetting.isSwipeUpEnabledDefaultValue(); return SwipeUpSetting.isSwipeUpSettingAvailable() ? Settings.Secure.getInt( @@ -286,24 +297,49 @@ public final class LauncherInstrumentation { // We need waiting for any accessibility event generated after pressing Home because // otherwise waitForIdle may return immediately in case when there was a big enough pause in // accessibility events prior to pressing Home. - executeAndWaitForEvent( - () -> { - log("LauncherInstrumentation.pressHome before clicking"); - waitForSystemUiObject("home").click(); - }, - event -> true, - "Pressing Home didn't produce any events"); - mDevice.waitForIdle(); + if (getNavigationModel() == NavigationModel.ZERO_BUTTON) { + if (hasLauncherObject(WORKSPACE_RES_ID)) { + log("0-button pressHome: already in workspace"); + } else if (hasLauncherObject(OVERVIEW_RES_ID)) { + log("0-button pressHome: overview"); + mDevice.pressHome(); + } else if (hasLauncherObject(WIDGETS_RES_ID)) { + log("0-button pressHome: widgets"); + mDevice.pressHome(); + } else if (hasLauncherObject(APPS_RES_ID)) { + log("0-button pressHome: all apps"); + mDevice.pressHome(); + } else { + log("0-button pressHome: another app"); + assertTrue("Launcher is visible, don't know how to go home", + !mDevice.hasObject(By.pkg(getLauncherPackageName()))); + final UiObject2 navBar = waitForSystemUiObject("navigation_bar_frame"); - // Temporarily press home twice as the first click sometimes gets ignored (b/124239413) - executeAndWaitForEvent( - () -> { - log("LauncherInstrumentation.pressHome before clicking"); - waitForSystemUiObject("home").click(); - }, - event -> true, - "Pressing Home didn't produce any events"); - mDevice.waitForIdle(); + swipe( + navBar.getVisibleBounds().centerX(), navBar.getVisibleBounds().centerY(), + navBar.getVisibleBounds().centerX(), 0, + BACKGROUND_APP_STATE_ORDINAL, ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME); + } + } else { + executeAndWaitForEvent( + () -> { + log("LauncherInstrumentation.pressHome before clicking"); + waitForSystemUiObject("home").click(); + }, + event -> true, + "Pressing Home didn't produce any events"); + mDevice.waitForIdle(); + + // Temporarily press home twice as the first click sometimes gets ignored (b/124239413) + executeAndWaitForEvent( + () -> { + log("LauncherInstrumentation.pressHome before clicking"); + waitForSystemUiObject("home").click(); + }, + event -> true, + "Pressing Home didn't produce any events"); + mDevice.waitForIdle(); + } return getWorkspace(); } @@ -424,6 +460,11 @@ public final class LauncherInstrumentation { return object; } + @Nullable + private boolean hasLauncherObject(String resId) { + return mDevice.hasObject(getLauncherObjectSelector(resId)); + } + @NonNull UiObject2 waitForLauncherObject(String resName) { final BySelector selector = getLauncherObjectSelector(resName); @@ -446,8 +487,12 @@ public final class LauncherInstrumentation { } void swipe(int startX, int startY, int endX, int endY, int expectedState) { + swipe(startX, startY, endX, endY, expectedState, 60); + } + + void swipe(int startX, int startY, int endX, int endY, int expectedState, int steps) { final Bundle parcel = (Bundle) executeAndWaitForEvent( - () -> mDevice.swipe(startX, startY, endX, endY, 60), + () -> mDevice.swipe(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); @@ -459,13 +504,6 @@ public final class LauncherInstrumentation { mDevice.waitForIdle(); } - void sendPointer(int action, Point point) { - final MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(), - SystemClock.uptimeMillis(), action, point.x, point.y, 0); - mInstrumentation.sendPointerSync(event); - event.recycle(); - } - float getDisplayDensity() { return mInstrumentation.getTargetContext().getResources().getDisplayMetrics().density; } @@ -473,4 +511,51 @@ public final class LauncherInstrumentation { int getTouchSlop() { return ViewConfiguration.get(getContext()).getScaledTouchSlop(); } + + private static MotionEvent getMotionEvent(long downTime, long eventTime, int action, + float x, float y) { + MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties(); + properties.id = 0; + properties.toolType = Configurator.getInstance().getToolType(); + + MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); + coords.pressure = 1; + coords.size = 1; + coords.x = x; + coords.y = y; + + return MotionEvent.obtain(downTime, eventTime, action, 1, + new MotionEvent.PointerProperties[]{properties}, + new MotionEvent.PointerCoords[]{coords}, + 0, 0, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); + } + + void sendPointer(long downTime, long currentTime, int action, Point point) { + final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y); + mInstrumentation.getUiAutomation().injectInputEvent(event, true); + event.recycle(); + } + + void movePointer(long downTime, long duration, Point from, Point to) { + final Point point = new Point(); + for (; ; ) { + sleep(16); + + final long currentTime = SystemClock.uptimeMillis(); + final float progress = (currentTime - downTime) / (float) duration; + if (progress > 1) return; + + point.x = from.x + (int) (progress * (to.x - from.x)); + point.y = from.y + (int) (progress * (to.y - from.y)); + + sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point); + } + } + + static void sleep(int duration) { + try { + Thread.sleep(duration); + } catch (InterruptedException e) { + } + } } \ No newline at end of file diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java index e3ef74ab73..7d97acd754 100644 --- a/tests/tapl/com/android/launcher3/tapl/Workspace.java +++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java @@ -136,6 +136,8 @@ public final class Workspace extends Home { */ public void flingForward() { final UiObject2 workspace = verifyActiveContainer(); + final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1; + workspace.setGestureMargins(0, 0, margin, 0); workspace.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity())); mLauncher.waitForIdle(); verifyActiveContainer(); @@ -147,6 +149,8 @@ public final class Workspace extends Home { */ public void flingBackward() { final UiObject2 workspace = verifyActiveContainer(); + final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1; + workspace.setGestureMargins(margin, 0, 0, 0); workspace.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity())); mLauncher.waitForIdle(); verifyActiveContainer(); From 896a715a1c06019e1ba4916dd5b7739f1e818070 Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Thu, 21 Mar 2019 14:06:02 -0700 Subject: [PATCH 60/68] Reconcile difference btw master and ub-launcher3-master Change-Id: I33dcd6a062b754edbe2da9c7fcf2a8ccfb41b661 --- Android.mk | 6 +++++- tests/Android.mk | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Android.mk b/Android.mk index 2feb1419a2..25593e642c 100644 --- a/Android.mk +++ b/Android.mk @@ -24,7 +24,10 @@ LOCAL_USE_AAPT2 := true LOCAL_AAPT2_ONLY := true LOCAL_MODULE_TAGS := optional -LOCAL_STATIC_JAVA_LIBRARIES := libPluginCore +ifneq (,$(wildcard frameworks/base)) +else + LOCAL_STATIC_JAVA_LIBRARIES:= libPluginCore +endif LOCAL_SRC_FILES := \ $(call all-java-files-under, src_plugins) @@ -197,6 +200,7 @@ LOCAL_PACKAGE_NAME := Launcher3QuickStep LOCAL_PRIVILEGED_MODULE := true LOCAL_PRODUCT_MODULE := true LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 +LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3 LOCAL_RESOURCE_DIR := \ $(LOCAL_PATH)/quickstep/res \ diff --git a/tests/Android.mk b/tests/Android.mk index a787537542..bf5cb29338 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -23,8 +23,12 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ androidx.annotation_annotation \ androidx.test.runner \ androidx.test.rules \ - androidx.test.uiautomator_uiautomator \ - libSharedSystemUI + androidx.test.uiautomator_uiautomator + +ifneq (,$(wildcard frameworks/base)) +else + LOCAL_STATIC_JAVA_LIBRARIES += libSharedSystemUI +endif LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \ ../quickstep/src/com/android/quickstep/SwipeUpSetting.java \ From fefe8b6d0edc9c397d54e16484650ca89385ea67 Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 18 Mar 2019 12:59:40 -0700 Subject: [PATCH 61/68] Add clear all button to Recents Go Add a button to clear all tasks to the recents Go layout and hook it up to the model. Bug: 114136250 Test: Open recents, hit clear all Change-Id: Id67310ce88053528d372e4dc21e2b011ed957646 --- .../res/drawable/clear_all_button.xml | 22 +++++++++++++++ .../res/layout/icon_recents_root_view.xml | 28 +++++++++++++++---- go/quickstep/res/values/colors.xml | 20 +++++++++++++ go/quickstep/res/values/dimens.xml | 3 ++ .../quickstep/TaskInputController.java | 12 ++++++-- .../com/android/quickstep/TaskListLoader.java | 7 +++++ .../quickstep/views/IconRecentsView.java | 11 ++++++-- 7 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 go/quickstep/res/drawable/clear_all_button.xml create mode 100644 go/quickstep/res/values/colors.xml diff --git a/go/quickstep/res/drawable/clear_all_button.xml b/go/quickstep/res/drawable/clear_all_button.xml new file mode 100644 index 0000000000..acac32de5e --- /dev/null +++ b/go/quickstep/res/drawable/clear_all_button.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/go/quickstep/res/layout/icon_recents_root_view.xml b/go/quickstep/res/layout/icon_recents_root_view.xml index ff3dcef989..fddb1d347e 100644 --- a/go/quickstep/res/layout/icon_recents_root_view.xml +++ b/go/quickstep/res/layout/icon_recents_root_view.xml @@ -19,13 +19,31 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> - + android:orientation="vertical" + android:visibility="gone"> + +