diff --git a/Android.mk b/Android.mk index 3d1d996dfd..5def65f50c 100644 --- a/Android.mk +++ b/Android.mk @@ -200,7 +200,7 @@ LOCAL_RESOURCE_DIR := \ $(LOCAL_PATH)/quickstep/recents_ui_overrides/res LOCAL_FULL_LIBS_MANIFEST_FILES := \ - $(LOCAL_PATH)/AndroidManifest.xml \ + $(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \ $(LOCAL_PATH)/AndroidManifest-common.xml LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml @@ -247,7 +247,7 @@ LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3 LOCAL_FULL_LIBS_MANIFEST_FILES := \ $(LOCAL_PATH)/go/AndroidManifest.xml \ - $(LOCAL_PATH)/AndroidManifest.xml \ + $(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \ $(LOCAL_PATH)/AndroidManifest-common.xml LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml @@ -293,7 +293,7 @@ LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3 LOCAL_FULL_LIBS_MANIFEST_FILES := \ $(LOCAL_PATH)/go/AndroidManifest.xml \ - $(LOCAL_PATH)/AndroidManifest.xml \ + $(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \ $(LOCAL_PATH)/AndroidManifest-common.xml LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml index 8db875be90..555cc7302b 100644 --- a/AndroidManifest-common.xml +++ b/AndroidManifest-common.xml @@ -169,7 +169,7 @@ diff --git a/OWNERS b/OWNERS index 538ca33d8f..7340e84883 100644 --- a/OWNERS +++ b/OWNERS @@ -10,6 +10,7 @@ mrcasey@google.com sunnygoyal@google.com twickham@google.com winsonc@google.com +zakcohen@google.com per-file FeatureFlags.java = sunnygoyal@google.com, adamcohen@google.com per-file BaseFlags.java = sunnygoyal@google.com, adamcohen@google.com diff --git a/go/AndroidManifest.xml b/go/AndroidManifest.xml index fae1efffe0..f84a82eaa2 100644 --- a/go/AndroidManifest.xml +++ b/go/AndroidManifest.xml @@ -22,7 +22,7 @@ xmlns:tools="http://schemas.android.com/tools" package="com.android.launcher3" > - + com.android.quickstep.InstantAppResolverImpl com.android.quickstep.QuickstepProcessInitializer + + com.android.quickstep.logging.UserEventDispatcherExtension diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/go/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java new file mode 100644 index 0000000000..0c60468eef --- /dev/null +++ b/go/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.uioverrides; + +import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController; +import com.android.launcher3.uioverrides.touchcontrollers.LandscapeStatesTouchController; +import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController; +import com.android.launcher3.util.TouchController; +import com.android.quickstep.SysUINavigationMode; + +import java.util.ArrayList; + +public class QuickstepLauncher extends BaseQuickstepLauncher { + + public static final boolean GO_LOW_RAM_RECENTS_ENABLED = true; + + @Override + public TouchController[] createTouchControllers() { + ArrayList list = new ArrayList<>(); + list.add(getDragController()); + + if (getDeviceProfile().isVerticalBarLayout()) { + list.add(new LandscapeStatesTouchController(this)); + list.add(new LandscapeEdgeSwipeController(this)); + } else { + boolean allowDragToOverview = SysUINavigationMode.INSTANCE.get(this) + .getMode().hasGestures; + list.add(new PortraitStatesTouchController(this, allowDragToOverview)); + } + return list.toArray(new TouchController[list.size()]); + } +} diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java deleted file mode 100644 index f2aa842d12..0000000000 --- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.uioverrides; - -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherStateManager.StateHandler; -import com.android.launcher3.Utilities; -import com.android.launcher3.graphics.RotationMode; -import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController; -import com.android.launcher3.uioverrides.touchcontrollers.LandscapeStatesTouchController; -import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController; -import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController; -import com.android.launcher3.util.TouchController; -import com.android.quickstep.SysUINavigationMode; -import com.android.quickstep.views.IconRecentsView; - -import java.util.ArrayList; - -/** - * Provides recents-related {@link UiFactory} logic and classes. - */ -public abstract class RecentsUiFactory { - - public static final boolean GO_LOW_RAM_RECENTS_ENABLED = true; - - public static TouchController[] createTouchControllers(Launcher launcher) { - ArrayList list = new ArrayList<>(); - list.add(launcher.getDragController()); - - if (launcher.getDeviceProfile().isVerticalBarLayout()) { - list.add(new LandscapeStatesTouchController(launcher)); - list.add(new LandscapeEdgeSwipeController(launcher)); - } else { - boolean allowDragToOverview = SysUINavigationMode.INSTANCE.get(launcher) - .getMode().hasGestures; - list.add(new PortraitStatesTouchController(launcher, allowDragToOverview)); - } - if (Utilities.IS_DEBUG_DEVICE - && !launcher.getDeviceProfile().isMultiWindowMode - && !launcher.getDeviceProfile().isVerticalBarLayout()) { - list.add(new StatusBarTouchController(launcher)); - } - return list.toArray(new TouchController[list.size()]); - } - - /** - * Creates and returns the controller responsible for recents view state transitions. - * - * @param launcher the launcher activity - * @return state handler for recents - */ - public static StateHandler createRecentsViewStateController(Launcher launcher) { - return new RecentsViewStateController(launcher); - } - - /** - * Clean-up logic that occurs when recents is no longer in use/visible. - * - * @param launcher the launcher activity - */ - public static void resetOverview(Launcher launcher) { - IconRecentsView recentsView = launcher.getOverviewPanel(); - recentsView.setTransitionedFromApp(false); - } - - /** - * Recents logic that triggers when launcher state changes or launcher activity stops/resumes. - * - * @param launcher the launcher activity - */ - public static void onLauncherStateOrResumeChanged(Launcher launcher) {} - - public static RotationMode getRotationMode(DeviceProfile dp) { - return RotationMode.NORMAL; - } - - public static void clearSwipeSharedState(Launcher launcher, boolean finishAnimation) { } -} diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java index 6b50088221..04753d2527 100644 --- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -46,13 +46,13 @@ final class AppToOverviewAnimationProvider imple RemoteAnimationProvider { private static final String TAG = "AppToOverviewAnimationProvider"; - private final BaseActivityInterface mHelper; + private final BaseActivityInterface mActivityInterface; private final int mTargetTaskId; private IconRecentsView mRecentsView; private AppToOverviewAnimationListener mAnimationReadyListener; - AppToOverviewAnimationProvider(BaseActivityInterface helper, int targetTaskId) { - mHelper = helper; + AppToOverviewAnimationProvider(BaseActivityInterface activityInterface, int targetTaskId) { + mActivityInterface = activityInterface; mTargetTaskId = targetTaskId; } @@ -68,15 +68,15 @@ final class AppToOverviewAnimationProvider imple /** * Callback for when the activity is ready/initialized. * - * @param activity the activity that is ready * @param wasVisible true if it was visible before */ - boolean onActivityReady(T activity, Boolean wasVisible) { + boolean onActivityReady(Boolean wasVisible) { + T activity = mActivityInterface.getCreatedActivity(); if (mAnimationReadyListener != null) { mAnimationReadyListener.onActivityReady(activity); } BaseActivityInterface.AnimationFactory factory = - mHelper.prepareRecentsUI(activity, wasVisible, + mActivityInterface.prepareRecentsUI(wasVisible, false /* animate activity */, (controller) -> { controller.dispatchOnStart(); ValueAnimator anim = controller.getAnimationPlayer() diff --git a/go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java index 2af8441853..ecb9472c72 100644 --- a/go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java +++ b/go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java @@ -29,8 +29,8 @@ import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.views.IconRecentsView; -import java.util.function.BiPredicate; import java.util.function.Consumer; +import java.util.function.Predicate; /** * {@link BaseActivityInterface} for recents when the default launcher is different than the @@ -43,12 +43,13 @@ public final class FallbackActivityInterface extends public FallbackActivityInterface() { } @Override - public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible, + public AnimationFactory prepareRecentsUI(boolean activityVisible, boolean animateActivity, Consumer callback) { if (activityVisible) { return (transitionLength) -> { }; } + RecentsActivity activity = getCreatedActivity(); IconRecentsView rv = activity.getOverviewPanel(); rv.setUsingRemoteAnimation(true); rv.setAlpha(0); @@ -84,8 +85,9 @@ public final class FallbackActivityInterface extends @Override public ActivityInitListener createActivityInitListener( - BiPredicate onInitListener) { - return new ActivityInitListener(onInitListener, RecentsActivity.ACTIVITY_TRACKER); + Predicate onInitListener) { + return new ActivityInitListener<>((activity, alreadyOnHome) -> + onInitListener.test(alreadyOnHome), RecentsActivity.ACTIVITY_TRACKER); } @Nullable @@ -115,5 +117,5 @@ public final class FallbackActivityInterface extends } @Override - public void onLaunchTaskSuccess(RecentsActivity activity) { } + public void onLaunchTaskSuccess() { } } diff --git a/go/quickstep/src/com/android/quickstep/GoActivityInterface.java b/go/quickstep/src/com/android/quickstep/GoActivityInterface.java index 5ce0f4cdf5..b62d17ce2f 100644 --- a/go/quickstep/src/com/android/quickstep/GoActivityInterface.java +++ b/go/quickstep/src/com/android/quickstep/GoActivityInterface.java @@ -17,7 +17,7 @@ public abstract class GoActivityInterface implem BaseActivityInterface { @Override - public void onTransitionCancelled(T activity, boolean activityVisible) { + public void onTransitionCancelled(boolean activityVisible) { // Go transitions to overview are all atomic. } @@ -29,7 +29,7 @@ public abstract class GoActivityInterface implem } @Override - public void onSwipeUpToRecentsComplete(T activity) { + public void onSwipeUpToRecentsComplete() { // Go does not support swipe up gesture. } @@ -39,7 +39,7 @@ public abstract class GoActivityInterface implem } @Override - public HomeAnimationFactory prepareHomeUI(T activity) { + public HomeAnimationFactory prepareHomeUI() { // Go does not support gestures from app to home. return null; } @@ -63,7 +63,7 @@ public abstract class GoActivityInterface implem } @Override - public void onLaunchTaskFailed(T activity) { + public void onLaunchTaskFailed() { // Go does not support gestures from one task to another. } } diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java index 5bff8e8060..3e93480a95 100644 --- a/go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java +++ b/go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java @@ -26,8 +26,8 @@ import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.quickstep.views.IconRecentsView; -import java.util.function.BiPredicate; import java.util.function.Consumer; +import java.util.function.Predicate; /** * {@link BaseActivityInterface} for the in-launcher recents. @@ -36,15 +36,15 @@ import java.util.function.Consumer; public final class LauncherActivityInterface extends GoActivityInterface { @Override - public AnimationFactory prepareRecentsUI(Launcher activity, - boolean activityVisible, boolean animateActivity, + public AnimationFactory prepareRecentsUI(boolean activityVisible, boolean animateActivity, Consumer callback) { - LauncherState fromState = activity.getStateManager().getState(); - activity.getOverviewPanel().setUsingRemoteAnimation(true); + Launcher launcher = getCreatedActivity(); + LauncherState fromState = launcher.getStateManager().getState(); + launcher.getOverviewPanel().setUsingRemoteAnimation(true); //TODO: Implement this based off where the recents view needs to be for app => recents anim. return new AnimationFactory() { public void createActivityInterface(long transitionLength) { - callback.accept(activity.getStateManager().createAnimationToNewWorkspace( + callback.accept(launcher.getStateManager().createAnimationToNewWorkspace( fromState, OVERVIEW, transitionLength)); } @@ -54,9 +54,9 @@ public final class LauncherActivityInterface extends GoActivityInterface onInitListener) { - return new LauncherInitListener(onInitListener); + public LauncherInitListener createActivityInitListener(Predicate onInitListener) { + return new LauncherInitListener((activity, alreadyOnHome) -> + onInitListener.test(alreadyOnHome)); } @Override @@ -105,7 +105,8 @@ public final class LauncherActivityInterface extends GoActivityInterface onInitListener) { - super(onInitListener); +/** Empty class, only exists so that lowRamWithQuickstepIconRecentsDebug compiles. */ +public class ShelfPeekAnim { + public ShelfPeekAnim(Launcher launcher) { } - @Override - public boolean init(Launcher launcher, boolean alreadyOnHome) { - PredictionUiStateManager.INSTANCE.get(launcher).switchClient(Client.OVERVIEW); - return super.init(launcher, alreadyOnHome); + public enum ShelfAnimState { } } diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index 87b4d4e2e5..e380698d38 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -40,6 +40,7 @@ import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; +import android.os.UserHandle; import android.util.ArraySet; import android.util.AttributeSet; import android.util.FloatProperty; @@ -66,6 +67,7 @@ import com.android.launcher3.R; import com.android.launcher3.util.Themes; import com.android.quickstep.ContentFillItemAnimator; import com.android.quickstep.RecentsModel; +import com.android.quickstep.RecentsModel.TaskVisualsChangeListener; import com.android.quickstep.RecentsToActivityHelper; import com.android.quickstep.TaskActionController; import com.android.quickstep.TaskAdapter; @@ -74,6 +76,7 @@ import com.android.quickstep.TaskListLoader; import com.android.quickstep.TaskSwipeCallback; import com.android.quickstep.util.MultiValueUpdateListener; import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; @@ -87,7 +90,8 @@ import java.util.Optional; * Root view for the icon recents view. Acts as the main interface to the rest of the Launcher code * base. */ -public final class IconRecentsView extends FrameLayout implements Insettable { +public final class IconRecentsView extends FrameLayout + implements Insettable, TaskVisualsChangeListener { public static final FloatProperty CONTENT_ALPHA = new FloatProperty("contentAlpha") { @@ -159,22 +163,6 @@ public final class IconRecentsView extends FrameLayout implements Insettable { private AnimatorSet mLayoutAnimation; private final ArraySet mLayingOutViews = new ArraySet<>(); private Rect mInsets; - private final RecentsModel.TaskThumbnailChangeListener listener = (taskId, thumbnailData) -> { - ArrayList itemViews = getTaskViews(); - for (int i = 0, size = itemViews.size(); i < size; i++) { - TaskItemView taskView = itemViews.get(i); - TaskHolder taskHolder = (TaskHolder) mTaskRecyclerView.getChildViewHolder(taskView); - Optional optTask = taskHolder.getTask(); - if (optTask.filter(task -> task.key.id == taskId).isPresent()) { - Task task = optTask.get(); - // Update thumbnail on the task. - task.thumbnail = thumbnailData; - taskView.setThumbnail(thumbnailData); - return task; - } - } - return null; - }; public IconRecentsView(Context context, AttributeSet attrs) { super(context, attrs); @@ -189,9 +177,29 @@ public final class IconRecentsView extends FrameLayout implements Insettable { mActivity.getStatsLogManager()); mTaskAdapter.setActionController(mTaskActionController); mTaskLayoutManager = new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */); - RecentsModel.INSTANCE.get(context).addThumbnailChangeListener(listener); } + @Override + public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) { + ArrayList itemViews = getTaskViews(); + for (int i = 0, size = itemViews.size(); i < size; i++) { + TaskItemView taskView = itemViews.get(i); + TaskHolder taskHolder = (TaskHolder) mTaskRecyclerView.getChildViewHolder(taskView); + Optional optTask = taskHolder.getTask(); + if (optTask.filter(task -> task.key.id == taskId).isPresent()) { + Task task = optTask.get(); + // Update thumbnail on the task. + task.thumbnail = thumbnailData; + taskView.setThumbnail(thumbnailData); + return task; + } + } + return null; + } + + @Override + public void onTaskIconChanged(String pkg, UserHandle user) { } + @Override protected void onFinishInflate() { super.onFinishInflate(); @@ -274,6 +282,18 @@ public final class IconRecentsView extends FrameLayout implements Insettable { } } + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this); + } + @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java index 5c4f37ca98..bce4e0f330 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java +++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java @@ -44,6 +44,7 @@ public class BaseIconFactory implements AutoCloseable { private final PackageManager mPm; private final ColorExtractor mColorExtractor; private boolean mDisableColorExtractor; + private boolean mBadgeOnLeft = false; protected final int mFillResIconDpi; protected final int mIconBitmapSize; @@ -77,6 +78,7 @@ public class BaseIconFactory implements AutoCloseable { protected void clear() { mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND; mDisableColorExtractor = false; + mBadgeOnLeft = false; } public ShadowGenerator getShadowGenerator() { @@ -116,7 +118,7 @@ public class BaseIconFactory implements AutoCloseable { icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f); } - return BitmapInfo.fromBitmap(icon, mDisableColorExtractor ? null : mColorExtractor); + return BitmapInfo.of(icon, extractColor(icon)); } public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, @@ -183,7 +185,10 @@ public class BaseIconFactory implements AutoCloseable { bitmap = createIconBitmap(badged, 1f); } } - return BitmapInfo.fromBitmap(bitmap, mDisableColorExtractor ? null : mColorExtractor); + int color = extractColor(bitmap); + return icon instanceof BitmapInfo.Extender + ? ((BitmapInfo.Extender) icon).getExtendedInfo(bitmap, color, this) + : BitmapInfo.of(bitmap, color); } public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) { @@ -194,6 +199,13 @@ public class BaseIconFactory implements AutoCloseable { Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds))); } + /** + * Switches badging to left/right + */ + public void setBadgeOnLeft(boolean badgeOnLeft) { + mBadgeOnLeft = badgeOnLeft; + } + /** * Sets the background color used for wrapped adaptive icon */ @@ -255,8 +267,12 @@ public class BaseIconFactory implements AutoCloseable { */ public void badgeWithDrawable(Canvas target, Drawable badge) { int badgeSize = getBadgeSizeForIconSize(mIconBitmapSize); - badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize, - mIconBitmapSize, mIconBitmapSize); + if (mBadgeOnLeft) { + badge.setBounds(0, mIconBitmapSize - badgeSize, badgeSize, mIconBitmapSize); + } else { + badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize, + mIconBitmapSize, mIconBitmapSize); + } badge.draw(target); } @@ -334,6 +350,10 @@ public class BaseIconFactory implements AutoCloseable { iconDpi); } + private int extractColor(Bitmap bitmap) { + return mDisableColorExtractor ? 0 : mColorExtractor.findDominantColorByHue(bitmap); + } + /** * Returns the correct badge size given an icon size */ diff --git a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java index 245561ea53..d33f9b1112 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java +++ b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java @@ -18,32 +18,55 @@ package com.android.launcher3.icons; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; +import androidx.annotation.NonNull; + public class BitmapInfo { public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8); + public static final BitmapInfo LOW_RES_INFO = fromBitmap(LOW_RES_ICON); - public Bitmap icon; - public int color; + public final Bitmap icon; + public final int color; - public void applyTo(BitmapInfo info) { - info.icon = icon; - info.color = color; + public BitmapInfo(Bitmap icon, int color) { + this.icon = icon; + this.color = color; + } + + /** + * Ideally icon should not be null, except in cases when generating hardware bitmap failed + */ + public final boolean isNullOrLowRes() { + return icon == null || icon == LOW_RES_ICON; } public final boolean isLowRes() { return LOW_RES_ICON == icon; } - public static BitmapInfo fromBitmap(Bitmap bitmap) { - return fromBitmap(bitmap, null); + public static BitmapInfo fromBitmap(@NonNull Bitmap bitmap) { + return of(bitmap, 0); } - public static BitmapInfo fromBitmap(Bitmap bitmap, ColorExtractor dominantColorExtractor) { - BitmapInfo info = new BitmapInfo(); - info.icon = bitmap; - info.color = dominantColorExtractor != null - ? dominantColorExtractor.findDominantColorByHue(bitmap) - : 0; - return info; + public static BitmapInfo of(@NonNull Bitmap bitmap, int color) { + return new BitmapInfo(bitmap, color); + } + + /** + * Interface to be implemented by drawables to provide a custom BitmapInfo + */ + public interface Extender { + + /** + * Called for creating a custom BitmapInfo + */ + default BitmapInfo getExtendedInfo(Bitmap bitmap, int color, BaseIconFactory iconFactory) { + return BitmapInfo.of(bitmap, color); + } + + /** + * Notifies the drawable that it will be drawn directly in the UI, without any preprocessing + */ + default void prepareToDrawOnUi() { } } } diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java index 93f0538bd5..6f63d88722 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java +++ b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java @@ -71,7 +71,10 @@ public abstract class BaseIconCache { // Empty class name is used for storing package default entry. public static final String EMPTY_CLASS_NAME = "."; - public static class CacheEntry extends BitmapInfo { + public static class CacheEntry { + + @NonNull + public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO; public CharSequence title = ""; public CharSequence contentDescription = ""; } @@ -259,23 +262,23 @@ public abstract class BaseIconCache { if (!replaceExisting) { entry = mCache.get(key); // We can't reuse the entry if the high-res icon is not present. - if (entry == null || entry.icon == null || entry.isLowRes()) { + if (entry == null || entry.bitmap.isNullOrLowRes()) { entry = null; } } if (entry == null) { entry = new CacheEntry(); - cachingLogic.loadIcon(mContext, object, entry); + entry.bitmap = cachingLogic.loadIcon(mContext, object); } // Icon can't be loaded from cachingLogic, which implies alternative icon was loaded // (e.g. fallback icon, default icon). So we drop here since there's no point in caching // an empty entry. - if (entry.icon == null) return; + if (entry.bitmap.isNullOrLowRes()) return; entry.title = cachingLogic.getLabel(object); entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user); if (cachingLogic.addToMemCache()) mCache.put(key, entry); - ContentValues values = newContentValues(entry, entry.title.toString(), + ContentValues values = newContentValues(entry.bitmap, entry.title.toString(), componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList)); addIconToDB(values, componentName, info, userSerial); } @@ -300,8 +303,8 @@ public abstract class BaseIconCache { return mDefaultIcons.get(user); } - public boolean isDefaultIcon(Bitmap icon, UserHandle user) { - return getDefaultIcon(user).icon == icon; + public boolean isDefaultIcon(BitmapInfo icon, UserHandle user) { + return getDefaultIcon(user).icon == icon.icon; } /** @@ -315,7 +318,7 @@ public abstract class BaseIconCache { assertWorkerThread(); ComponentKey cacheKey = new ComponentKey(componentName, user); CacheEntry entry = mCache.get(cacheKey); - if (entry == null || (entry.isLowRes() && !useLowResIcon)) { + if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) { entry = new CacheEntry(); if (cachingLogic.addToMemCache()) { mCache.put(cacheKey, entry); @@ -330,7 +333,7 @@ public abstract class BaseIconCache { providerFetchedOnce = true; if (object != null) { - cachingLogic.loadIcon(mContext, object, entry); + entry.bitmap = cachingLogic.loadIcon(mContext, object); } else { if (usePackageIcon) { CacheEntry packageEntry = getEntryForPackageLocked( @@ -338,15 +341,15 @@ public abstract class BaseIconCache { if (packageEntry != null) { if (DEBUG) Log.d(TAG, "using package default icon for " + componentName.toShortString()); - packageEntry.applyTo(entry); + entry.bitmap = packageEntry.bitmap; entry.title = packageEntry.title; entry.contentDescription = packageEntry.contentDescription; } } - if (entry.icon == null) { + if (entry.bitmap == null) { if (DEBUG) Log.d(TAG, "using default icon for " + componentName.toShortString()); - getDefaultIcon(user).applyTo(entry); + entry.bitmap = getDefaultIcon(user); } } } @@ -390,10 +393,10 @@ public abstract class BaseIconCache { } if (icon != null) { BaseIconFactory li = getIconFactory(); - li.createIconBitmap(icon).applyTo(entry); + entry.bitmap = li.createIconBitmap(icon); li.close(); } - if (!TextUtils.isEmpty(title) && entry.icon != null) { + if (!TextUtils.isEmpty(title) && entry.bitmap.icon != null) { mCache.put(cacheKey, entry); } } @@ -413,7 +416,7 @@ public abstract class BaseIconCache { ComponentKey cacheKey = getPackageKey(packageName, user); CacheEntry entry = mCache.get(cacheKey); - if (entry == null || (entry.isLowRes() && !useLowResIcon)) { + if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) { entry = new CacheEntry(); boolean entryUpdated = true; @@ -438,8 +441,8 @@ public abstract class BaseIconCache { entry.title = appInfo.loadLabel(mPackageManager); entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user); - entry.icon = useLowResIcon ? LOW_RES_ICON : iconInfo.icon; - entry.color = iconInfo.color; + entry.bitmap = BitmapInfo.of( + useLowResIcon ? LOW_RES_ICON : iconInfo.icon, iconInfo.color); // Add the icon in the DB here, since these do not get written during // package updates. @@ -461,7 +464,7 @@ public abstract class BaseIconCache { return entry; } - private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) { + protected boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) { Cursor c = null; try { c = mIconDb.query( @@ -472,7 +475,7 @@ public abstract class BaseIconCache { Long.toString(getSerialNumberForUser(cacheKey.user))}); if (c.moveToNext()) { // Set the alpha to be 255, so that we never have a wrong color - entry.color = setColorAlphaBound(c.getInt(0), 255); + entry.bitmap = BitmapInfo.of(LOW_RES_ICON, setColorAlphaBound(c.getInt(0), 255)); entry.title = c.getString(1); if (entry.title == null) { entry.title = ""; @@ -482,13 +485,12 @@ public abstract class BaseIconCache { entry.title, cacheKey.user); } - if (lowRes) { - entry.icon = LOW_RES_ICON; - } else { + if (!lowRes) { byte[] data = c.getBlob(2); try { - entry.icon = BitmapFactory.decodeByteArray(data, 0, data.length, - mDecodeOptions); + entry.bitmap = BitmapInfo.of( + BitmapFactory.decodeByteArray(data, 0, data.length, mDecodeOptions), + entry.bitmap.color); } catch (Exception e) { } } return true; diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java index 3aa783a14c..a89ede7b34 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java +++ b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java @@ -21,6 +21,7 @@ import android.content.pm.PackageInfo; import android.os.LocaleList; import android.os.UserHandle; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.icons.BitmapInfo; @@ -33,7 +34,8 @@ public interface CachingLogic { CharSequence getLabel(T object); - void loadIcon(Context context, T object, BitmapInfo target); + @NonNull + BitmapInfo loadIcon(Context context, T object); /** * Provides a option list of keywords to associate with this object diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml new file mode 100644 index 0000000000..60afddb0a8 --- /dev/null +++ b/quickstep/AndroidManifest-launcher.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml b/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml new file mode 100644 index 0000000000..cfc6d4801d --- /dev/null +++ b/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml index 863a8ba528..ee672d47cc 100644 --- a/quickstep/recents_ui_overrides/res/values/dimens.xml +++ b/quickstep/recents_ui_overrides/res/values/dimens.xml @@ -28,4 +28,9 @@ 18dp 10dp -60dp + + + 15dp + 8dp + \ No newline at end of file diff --git a/quickstep/recents_ui_overrides/res/values/override.xml b/quickstep/recents_ui_overrides/res/values/override.xml index 1ddd3f594f..ed3ba929a8 100644 --- a/quickstep/recents_ui_overrides/res/values/override.xml +++ b/quickstep/recents_ui_overrides/res/values/override.xml @@ -24,5 +24,7 @@ com.android.launcher3.appprediction.PredictionAppTracker com.android.quickstep.QuickstepProcessInitializer + + com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java new file mode 100644 index 0000000000..fe9fd604e2 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3; + +import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionManager; +import android.app.prediction.AppPredictor; +import android.app.prediction.AppTarget; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.AppTargetId; +import android.content.ComponentName; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.Nullable; + +import com.android.launcher3.allapps.AllAppsStore; +import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.appprediction.ComponentKeyMapper; +import com.android.launcher3.appprediction.DynamicItemCache; +import com.android.launcher3.dragndrop.DragController; +import com.android.launcher3.dragndrop.DragOptions; +import com.android.launcher3.icons.IconCache; +import com.android.launcher3.popup.PopupContainerWithArrow; +import com.android.launcher3.popup.SystemShortcut; +import com.android.launcher3.shortcuts.ShortcutKey; +import com.android.launcher3.touch.ItemLongClickListener; +import com.android.launcher3.uioverrides.QuickstepLauncher; +import com.android.launcher3.util.ComponentKey; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Provides prediction ability for the hotseat. Fills gaps in hotseat with predicted items, allows + * pinning of predicted apps and manages replacement of predicted apps with user drag. + */ +public class HotseatPredictionController implements DragController.DragListener, + View.OnAttachStateChangeListener, SystemShortcut.Factory, + InvariantDeviceProfile.OnIDPChangeListener, AllAppsStore.OnUpdateListener, + IconCache.ItemInfoUpdateReceiver { + + private static final String TAG = "PredictiveHotseat"; + private static final boolean DEBUG = false; + + //TODO: replace this with AppTargetEvent.ACTION_UNPIN (b/144119543) + private static final int APPTARGET_ACTION_UNPIN = 4; + + private static final String PREDICTION_CLIENT = "hotseat"; + + private DropTarget.DragObject mDragObject; + private int mHotSeatItemsCount; + + private Launcher mLauncher; + private Hotseat mHotseat; + + private List mComponentKeyMappers = new ArrayList<>(); + + private DynamicItemCache mDynamicItemCache; + + private AppPredictor mAppPredictor; + private AllAppsStore mAllAppsStore; + + public HotseatPredictionController(Launcher launcher) { + mLauncher = launcher; + mHotseat = launcher.getHotseat(); + mAllAppsStore = mLauncher.getAppsView().getAppsStore(); + mAllAppsStore.addUpdateListener(this); + mDynamicItemCache = new DynamicItemCache(mLauncher, () -> fillGapsWithPrediction(false)); + mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons; + launcher.getDeviceProfile().inv.addOnChangeListener(this); + mHotseat.addOnAttachStateChangeListener(this); + } + + @Override + public void onViewAttachedToWindow(View view) { + mLauncher.getDragController().addDragListener(this); + } + + @Override + public void onViewDetachedFromWindow(View view) { + mLauncher.getDragController().removeDragListener(this); + } + + /** + * Fills gaps in the hotseat with predictions + */ + public void fillGapsWithPrediction(boolean animate) { + if (mDragObject != null) { + return; + } + List predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers); + int predictionIndex = 0; + ArrayList newItemsToAdd = new ArrayList<>(); + for (int rank = 0; rank < mHotSeatItemsCount; rank++) { + View child = mHotseat.getChildAt( + mHotseat.getCellXFromOrder(rank), + mHotseat.getCellYFromOrder(rank)); + + if (child != null && !isPredictedIcon(child)) { + continue; + } + if (predictedApps.size() <= predictionIndex) { + // Remove predicted apps from the past + if (isPredictedIcon(child)) { + mHotseat.removeView(child); + } + continue; + } + + WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++); + if (isPredictedIcon(child)) { + BubbleTextView icon = (BubbleTextView) child; + icon.applyFromWorkspaceItem(predictedItem); + } else { + newItemsToAdd.add(predictedItem); + } + preparePredictionInfo(predictedItem, rank); + } + mLauncher.bindItems(newItemsToAdd, animate); + for (BubbleTextView icon : getPredictedIcons()) { + icon.verifyHighRes(); + icon.setOnLongClickListener((v) -> { + PopupContainerWithArrow.showForIcon((BubbleTextView) v); + return true; + }); + icon.setBackgroundResource(R.drawable.predicted_icon_background); + } + } + + /** + * Unregisters callbacks and frees resources + */ + public void destroy() { + mAllAppsStore.removeUpdateListener(this); + mLauncher.getDeviceProfile().inv.removeOnChangeListener(this); + mHotseat.removeOnAttachStateChangeListener(this); + if (mAppPredictor != null) { + mAppPredictor.destroy(); + } + } + + /** + * Creates App Predictor with all the current apps pinned on the hotseat + */ + public void createPredictor() { + AppPredictionManager apm = mLauncher.getSystemService(AppPredictionManager.class); + if (apm == null) { + return; + } + if (mAppPredictor != null) { + mAppPredictor.destroy(); + } + mAppPredictor = apm.createAppPredictionSession( + new AppPredictionContext.Builder(mLauncher) + .setUiSurface(PREDICTION_CLIENT) + .setPredictedTargetCount(mHotSeatItemsCount) + .setExtras(getAppPredictionContextExtra()) + .build()); + mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(), + this::setPredictedApps); + mAppPredictor.requestPredictionUpdate(); + } + + private Bundle getAppPredictionContextExtra() { + Bundle bundle = new Bundle(); + ViewGroup vg = mHotseat.getShortcutsAndWidgets(); + ArrayList pinnedApps = new ArrayList<>(); + for (int i = 0; i < vg.getChildCount(); i++) { + View child = vg.getChildAt(i); + if (isPinnedIcon(child)) { + WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) child.getTag(); + pinnedApps.add(getAppTargetFromItemInfo(itemInfo)); + } + } + bundle.putParcelableArrayList("pinned_apps", pinnedApps); + return bundle; + } + + private void setPredictedApps(List appTargets) { + mComponentKeyMappers.clear(); + for (AppTarget appTarget : appTargets) { + ComponentKey key; + if (appTarget.getShortcutInfo() != null) { + key = ShortcutKey.fromInfo(appTarget.getShortcutInfo()); + } else { + key = new ComponentKey(new ComponentName(appTarget.getPackageName(), + appTarget.getClassName()), appTarget.getUser()); + } + mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache)); + } + updateDependencies(); + fillGapsWithPrediction(false); + } + + private void updateDependencies() { + mDynamicItemCache.updateDependencies(mComponentKeyMappers, mAllAppsStore, this, + mHotSeatItemsCount); + } + + private void pinPrediction(ItemInfo info) { + BubbleTextView icon = (BubbleTextView) mHotseat.getChildAt( + mHotseat.getCellXFromOrder(info.rank), + mHotseat.getCellYFromOrder(info.rank)); + if (icon == null) { + return; + } + WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo((WorkspaceItemInfo) info); + mLauncher.getModelWriter().addItemToDatabase(workspaceItemInfo, + LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId, + workspaceItemInfo.cellX, workspaceItemInfo.cellY); + ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start(); + icon.reset(); + icon.applyFromWorkspaceItem(workspaceItemInfo); + icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE); + AppTarget appTarget = getAppTargetFromItemInfo(workspaceItemInfo); + notifyItemAction(appTarget, AppTargetEvent.ACTION_PIN); + } + + private List mapToWorkspaceItemInfo( + List components) { + AllAppsStore allAppsStore = mLauncher.getAppsView().getAppsStore(); + if (allAppsStore.getApps().length == 0) { + return Collections.emptyList(); + } + + List predictedApps = new ArrayList<>(); + for (ComponentKeyMapper mapper : components) { + ItemInfoWithIcon info = mapper.getApp(allAppsStore); + if (info instanceof AppInfo) { + WorkspaceItemInfo predictedApp = new WorkspaceItemInfo((AppInfo) info); + predictedApps.add(predictedApp); + } else if (info instanceof WorkspaceItemInfo) { + predictedApps.add(new WorkspaceItemInfo((WorkspaceItemInfo) info)); + } else { + if (DEBUG) { + Log.e(TAG, "Predicted app not found: " + mapper); + } + } + // Stop at the number of hotseat items + if (predictedApps.size() == mHotSeatItemsCount) { + break; + } + } + return predictedApps; + } + + private List getPredictedIcons() { + List icons = new ArrayList<>(); + ViewGroup vg = mHotseat.getShortcutsAndWidgets(); + for (int i = 0; i < vg.getChildCount(); i++) { + View child = vg.getChildAt(i); + if (isPredictedIcon(child)) { + icons.add((BubbleTextView) child); + } + } + return icons; + } + + private void removePredictedApps(boolean animate) { + for (BubbleTextView icon : getPredictedIcons()) { + if (animate) { + icon.animate().scaleY(0).scaleX(0).setListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + if (icon.getParent() != null) { + mHotseat.removeView(icon); + } + } + }); + } else { + if (icon.getParent() != null) { + mHotseat.removeView(icon); + } + } + } + } + + private void notifyItemAction(AppTarget target, int action) { + if (mAppPredictor != null) { + mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target, action).build()); + } + } + + @Override + public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { + removePredictedApps(true); + mDragObject = dragObject; + } + + @Override + public void onDragEnd() { + if (mDragObject == null) { + return; + } + ItemInfo dragInfo = mDragObject.dragInfo; + if (dragInfo instanceof WorkspaceItemInfo && dragInfo.getTargetComponent() != null) { + if (isInHotseat(dragInfo) && !isInHotseat(mDragObject.originalDragInfo)) { + notifyItemAction(getAppTargetFromItemInfo(dragInfo), AppTargetEvent.ACTION_PIN); + } else if (!isInHotseat(dragInfo) && isInHotseat(mDragObject.originalDragInfo)) { + notifyItemAction(getAppTargetFromItemInfo(dragInfo), APPTARGET_ACTION_UNPIN); + } + } + mDragObject = null; + fillGapsWithPrediction(true); + } + + @Nullable + @Override + public SystemShortcut getShortcut(QuickstepLauncher activity, + ItemInfo itemInfo) { + if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) { + return null; + } + return new PinPrediction(activity, itemInfo); + } + + private void preparePredictionInfo(WorkspaceItemInfo itemInfo, int rank) { + itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; + itemInfo.rank = rank; + itemInfo.cellX = rank; + itemInfo.cellY = mHotSeatItemsCount - rank - 1; + itemInfo.screenId = rank; + } + + @Override + public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) { + this.mHotSeatItemsCount = profile.numHotseatIcons; + createPredictor(); + } + + @Override + public void onAppsUpdated() { + updateDependencies(); + fillGapsWithPrediction(false); + } + + @Override + public void reapplyItemInfo(ItemInfoWithIcon info) { + + } + + private class PinPrediction extends SystemShortcut { + + private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) { + super(R.drawable.ic_pin, R.string.pin_prediction, target, + itemInfo); + } + + @Override + public void onClick(View view) { + dismissTaskMenuView(mTarget); + pinPrediction(mItemInfo); + } + } + + private static boolean isPredictedIcon(View view) { + return view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo + && ((WorkspaceItemInfo) view.getTag()).container + == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; + } + + private static boolean isPinnedIcon(View view) { + if (!(view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo)) { + return false; + } + ItemInfo info = (ItemInfo) view.getTag(); + return info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && ( + info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION + || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT); + } + + private static boolean isInHotseat(ItemInfo itemInfo) { + return itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT; + } + + private static AppTarget getAppTargetFromItemInfo(ItemInfo info) { + if (info.getTargetComponent() == null) return null; + return new AppTarget.Builder( + new AppTargetId("app:" + info.getTargetComponent().getPackageName()), + info.getTargetComponent().getPackageName(), info.user).setClassName( + info.getTargetComponent().getClassName()).build(); + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java index d84248419e..6946508fc7 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java @@ -17,7 +17,10 @@ package com.android.launcher3; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; +import static com.android.launcher3.LauncherState.BACKGROUND_APP; +import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch; @@ -27,12 +30,15 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.content.Context; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.launcher3.LauncherState.ScaleAndTranslation; +import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.SpringAnimationBuilder; @@ -145,8 +151,37 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti @Override public Animator createStateElementAnimation(int index, float... values) { switch (index) { - case INDEX_SHELF_ANIM: - return mLauncher.getAllAppsController().createSpringAnimation(values); + case INDEX_SHELF_ANIM: { + AllAppsTransitionController aatc = mLauncher.getAllAppsController(); + Animator springAnim = aatc.createSpringAnimation(values); + + if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { + // Translate hotseat with the shelf until reaching overview. + float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher); + ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mLauncher); + float shiftRange = aatc.getShiftRange(); + if (values.length == 1) { + values = new float[] {aatc.getProgress(), values[0]}; + } + ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values); + hotseatAnim.addUpdateListener(anim -> { + float progress = (Float) anim.getAnimatedValue(); + if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) { + float hotseatShift = (progress - overviewProgress) * shiftRange; + mLauncher.getHotseat().setTranslationY(hotseatShift + sat.translationY); + } + }); + hotseatAnim.setInterpolator(LINEAR); + hotseatAnim.setDuration(springAnim.getDuration()); + + AnimatorSet anim = new AnimatorSet(); + anim.play(hotseatAnim); + anim.play(springAnim); + return anim; + } + + return springAnim; + } case INDEX_RECENTS_FADE_ANIM: return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(), RecentsView.CONTENT_ALPHA, values); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java index b9f4147f85..0712285aec 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java @@ -18,8 +18,6 @@ package com.android.launcher3.appprediction; import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER; -import android.content.Context; - import com.android.launcher3.AppInfo; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.allapps.AllAppsStore; @@ -29,11 +27,9 @@ import com.android.launcher3.util.ComponentKey; public class ComponentKeyMapper { protected final ComponentKey componentKey; - private final Context mContext; private final DynamicItemCache mCache; - public ComponentKeyMapper(Context context, ComponentKey key, DynamicItemCache cache) { - mContext = context; + public ComponentKeyMapper(ComponentKey key, DynamicItemCache cache) { componentKey = key; mCache = cache; } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java index 65e69b6046..38bb180ecb 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java @@ -18,6 +18,7 @@ package com.android.launcher3.appprediction; import static android.content.pm.PackageManager.MATCH_INSTANT; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER; import android.content.Context; import android.content.Intent; @@ -37,8 +38,10 @@ import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; +import com.android.launcher3.AppInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.WorkspaceItemInfo; +import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.shortcuts.DeepShortcutManager; @@ -72,6 +75,7 @@ public class DynamicItemCache { private final Handler mUiHandler; private final InstantAppResolver mInstantAppResolver; private final Runnable mOnUpdateCallback; + private final IconCache mIconCache; private final Map mShortcuts; private final Map mInstantApps; @@ -82,6 +86,7 @@ public class DynamicItemCache { mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage); mInstantAppResolver = InstantAppResolver.newInstance(context); mOnUpdateCallback = onUpdateCallback; + mIconCache = LauncherAppState.getInstance(mContext).getIconCache(); mShortcuts = new HashMap<>(); mInstantApps = new HashMap<>(); @@ -170,7 +175,7 @@ public class DynamicItemCache { if (!details.isEmpty()) { WorkspaceItemInfo si = new WorkspaceItemInfo(details.get(0), mContext); try (LauncherIcons li = LauncherIcons.obtain(mContext)) { - si.applyFrom(li.createShortcutIcon(details.get(0), true /* badged */, null)); + si.bitmap = li.createShortcutIcon(details.get(0), true /* badged */, null); } catch (Exception e) { if (DEBUG) { Log.e(TAG, "Error loading shortcut icon for " + shortcutKey.toString()); @@ -209,7 +214,7 @@ public class DynamicItemCache { InstantAppItemInfo info = new InstantAppItemInfo(intent, pkgName); IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache(); iconCache.getTitleAndIcon(info, false); - if (info.iconBitmap == null || iconCache.isDefaultIcon(info.iconBitmap, info.user)) { + if (info.bitmap.icon == null || iconCache.isDefaultIcon(info.bitmap, info.user)) { return null; } return info; @@ -240,4 +245,35 @@ public class DynamicItemCache { public WorkspaceItemInfo getShortcutInfo(ShortcutKey key) { return mShortcuts.get(key); } + + /** + * requests and caches icons for app targets + */ + public void updateDependencies(List componentKeyMappers, + AllAppsStore appsStore, IconCache.ItemInfoUpdateReceiver callback, int itemCount) { + List instantAppsToLoad = new ArrayList<>(); + List shortcutsToLoad = new ArrayList<>(); + int total = componentKeyMappers.size(); + for (int i = 0, count = 0; i < total && count < itemCount; i++) { + ComponentKeyMapper mapper = componentKeyMappers.get(i); + // Update instant apps + if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) { + instantAppsToLoad.add(mapper.getPackage()); + count++; + } else if (mapper.getComponentKey() instanceof ShortcutKey) { + shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey()); + count++; + } else { + // Reload high res icon + AppInfo info = (AppInfo) mapper.getApp(appsStore); + if (info != null) { + if (info.usingLowResIcon()) { + mIconCache.updateIconInBackground(callback, info); + } + count++; + } + } + } + cacheItems(shortcutsToLoad, instantAppsToLoad); + } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java index 1a59770a02..f9ee701ef2 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,33 +18,36 @@ package com.android.launcher3.appprediction; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.OVERVIEW; -import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER; import android.app.prediction.AppPredictor; import android.app.prediction.AppTarget; import android.content.ComponentName; import android.content.Context; -import com.android.launcher3.AppInfo; +import androidx.annotation.NonNull; + import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener; +import com.android.launcher3.ItemInfo; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager.StateListener; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener; -import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver; import com.android.launcher3.shortcuts.ShortcutKey; +import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.MainThreadInitializedObject; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.IntStream; /** * Handler responsible to updating the UI due to predicted apps changes. Operations: @@ -239,7 +242,7 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe key = new ComponentKey(new ComponentName(appTarget.getPackageName(), appTarget.getClassName()), appTarget.getUser()); } - state.apps.add(new ComponentKeyMapper(mContext, key, mDynamicItemCache)); + state.apps.add(new ComponentKeyMapper(key, mDynamicItemCache)); } } updateDependencies(state); @@ -250,33 +253,8 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe if (!state.isEnabled || mAppsView == null) { return; } - - IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache(); - List instantAppsToLoad = new ArrayList<>(); - List shortcutsToLoad = new ArrayList<>(); - int total = state.apps.size(); - for (int i = 0, count = 0; i < total && count < mMaxIconsPerRow; i++) { - ComponentKeyMapper mapper = state.apps.get(i); - // Update instant apps - if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) { - instantAppsToLoad.add(mapper.getPackage()); - count++; - } else if (mapper.getComponentKey() instanceof ShortcutKey) { - shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey()); - count++; - } else { - // Reload high res icon - AppInfo info = (AppInfo) mapper.getApp(mAppsView.getAppsStore()); - if (info != null) { - if (info.usingLowResIcon()) { - // TODO: Update icon cache to support null callbacks. - iconCache.updateIconInBackground(this, info); - } - count++; - } - } - } - mDynamicItemCache.cacheItems(shortcutsToLoad, instantAppsToLoad); + mDynamicItemCache.updateDependencies(state.apps, mAppsView.getAppsStore(), this, + mMaxIconsPerRow); } @Override @@ -322,6 +300,30 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe return mCurrentState; } + /** + * Fill in predicted_rank field based on app prediction. + * Only applicable when {@link ItemInfo#itemType} is one of the followings: + * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}, + * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT}, + * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT} + */ + public static void fillInPredictedRank( + @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) { + final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate(); + if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null + || (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION + && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT + && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) { + return; + } + final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user); + final List predictedApps = manager.getCurrentState().apps; + IntStream.range(0, predictedApps.size()) + .filter((i) -> k.equals(predictedApps.get(i).getComponentKey())) + .findFirst() + .ifPresent((rank) -> target.predictedRank = rank); + } + public static class PredictionState { public boolean isEnabled; diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java similarity index 67% rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java rename to quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 4c04b29598..744936b95e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -13,26 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.launcher3.uioverrides; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; -import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Rect; -import android.os.RemoteException; -import android.util.Log; +import android.os.Bundle; import android.view.Gravity; +import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.HotseatPredictionController; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; -import com.android.launcher3.LauncherStateManager.StateHandler; import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.graphics.RotationMode; +import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController; import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController; import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController; @@ -48,28 +49,20 @@ import com.android.launcher3.util.UiThreadHelper.AsyncCommand; import com.android.quickstep.RecentsModel; import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; -import com.android.quickstep.TouchInteractionService; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.views.RecentsView; import java.util.ArrayList; +import java.util.stream.Stream; -/** - * Provides recents-related {@link UiFactory} logic and classes. - */ -public abstract class RecentsUiFactory { - - private static final String TAG = RecentsUiFactory.class.getSimpleName(); +public class QuickstepLauncher extends BaseQuickstepLauncher { public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false; - /** * Reusable command for applying the shelf height on the background thread. */ - public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) -> { - SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2); - }; - + public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) -> + SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2); public static RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) { @Override public void mapRect(int left, int top, int right, int bottom, Rect out) { @@ -96,7 +89,6 @@ public abstract class RecentsUiFactory { } } }; - public static RotationMode ROTATION_SEASCAPE = new RotationMode(90) { @Override public void mapRect(int left, int top, int right, int bottom, Rect out) { @@ -142,83 +134,114 @@ public abstract class RecentsUiFactory { | horizontalGravity | verticalGravity; } }; + private HotseatPredictionController mHotseatPredictionController; - public static RotationMode getRotationMode(DeviceProfile dp) { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) { + mHotseatPredictionController = new HotseatPredictionController(this); + } + } + + @Override + protected RotationMode getFakeRotationMode(DeviceProfile dp) { return !dp.isVerticalBarLayout() ? RotationMode.NORMAL : (dp.isSeascape() ? ROTATION_SEASCAPE : ROTATION_LANDSCAPE); } - public static TouchController[] createTouchControllers(Launcher launcher) { - Mode mode = SysUINavigationMode.getMode(launcher); - - ArrayList list = new ArrayList<>(); - list.add(launcher.getDragController()); - if (mode == NO_BUTTON) { - list.add(new QuickSwitchTouchController(launcher)); - list.add(new NavBarToHomeTouchController(launcher)); - list.add(new FlingAndHoldTouchController(launcher)); - } else { - if (launcher.getDeviceProfile().isVerticalBarLayout()) { - list.add(new OverviewToAllAppsTouchController(launcher)); - list.add(new LandscapeEdgeSwipeController(launcher)); - if (mode.hasGestures) { - list.add(new TransposedQuickSwitchTouchController(launcher)); - } - } else { - list.add(new PortraitStatesTouchController(launcher, - mode.hasGestures /* allowDragToOverview */)); - if (mode.hasGestures) { - list.add(new QuickSwitchTouchController(launcher)); - } - } - } - - if (!launcher.getDeviceProfile().isMultiWindowMode) { - list.add(new StatusBarTouchController(launcher)); - } - - list.add(new LauncherTaskViewController(launcher)); - return list.toArray(new TouchController[list.size()]); + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + onStateOrResumeChanged(); } - /** - * Creates and returns the controller responsible for recents view state transitions. - * - * @param launcher the launcher activity - * @return state handler for recents - */ - public static StateHandler createRecentsViewStateController(Launcher launcher) { - return new RecentsViewStateController(launcher); + @Override + protected void onActivityFlagsChanged(int changeBits) { + super.onActivityFlagsChanged(changeBits); + + if ((changeBits & (ACTIVITY_STATE_DEFERRED_RESUMED | ACTIVITY_STATE_STARTED + | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0 + && (getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0) { + onStateOrResumeChanged(); + } } - /** Clears the swipe shared state for the current swipe gesture. */ - public static void clearSwipeSharedState(Launcher launcher, boolean finishAnimation) { - if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - launcher.getOverviewPanel().switchToScreenshot( - () -> TouchInteractionService.getSwipeSharedState().clearAllState( - finishAnimation)); + @Override + public Stream getSupportedShortcuts() { + if (mHotseatPredictionController != null) { + return Stream.concat(super.getSupportedShortcuts(), + Stream.of(mHotseatPredictionController)); } else { - TouchInteractionService.getSwipeSharedState().clearAllState(finishAnimation); + return super.getSupportedShortcuts(); } } /** * Recents logic that triggers when launcher state changes or launcher activity stops/resumes. - * - * @param launcher the launcher activity */ - public static void onLauncherStateOrResumeChanged(Launcher launcher) { - LauncherState state = launcher.getStateManager().getState(); - DeviceProfile profile = launcher.getDeviceProfile(); - boolean visible = (state == NORMAL || state == OVERVIEW) && launcher.isUserActive() + private void onStateOrResumeChanged() { + LauncherState state = getStateManager().getState(); + DeviceProfile profile = getDeviceProfile(); + boolean visible = (state == NORMAL || state == OVERVIEW) && isUserActive() && !profile.isVerticalBarLayout(); - UiThreadHelper.runAsyncCommand(launcher, SET_SHELF_HEIGHT, visible ? 1 : 0, + UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0, profile.hotseatBarSizePx); if (state == NORMAL) { - launcher.getOverviewPanel().setSwipeDownShouldLaunchApp(false); + ((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false); } } + @Override + public void finishBindingItems(int pageBoundFirst) { + super.finishBindingItems(pageBoundFirst); + if (mHotseatPredictionController != null) { + mHotseatPredictionController.createPredictor(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mHotseatPredictionController != null) { + mHotseatPredictionController.destroy(); + } + } + + @Override + public TouchController[] createTouchControllers() { + Mode mode = SysUINavigationMode.getMode(this); + + ArrayList list = new ArrayList<>(); + list.add(getDragController()); + if (mode == NO_BUTTON) { + list.add(new QuickSwitchTouchController(this)); + list.add(new NavBarToHomeTouchController(this)); + list.add(new FlingAndHoldTouchController(this)); + } else { + if (getDeviceProfile().isVerticalBarLayout()) { + list.add(new OverviewToAllAppsTouchController(this)); + list.add(new LandscapeEdgeSwipeController(this)); + if (mode.hasGestures) { + list.add(new TransposedQuickSwitchTouchController(this)); + } + } else { + list.add(new PortraitStatesTouchController(this, + mode.hasGestures /* allowDragToOverview */)); + if (mode.hasGestures) { + list.add(new QuickSwitchTouchController(this)); + } + } + } + + if (!getDeviceProfile().isMultiWindowMode) { + list.add(new StatusBarTouchController(this)); + } + + list.add(new LauncherTaskViewController(this)); + return list.toArray(new TouchController[list.size()]); + } + private static final class LauncherTaskViewController extends TaskViewTouchController { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java index e4e60a04b1..bb66ae1cf9 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java @@ -69,7 +69,7 @@ public class BackgroundAppState extends OverviewState { return super.getOverviewScaleAndTranslation(launcher); } TaskView dummyTask; - if (recentsView.getCurrentPage() >= 0) { + if (recentsView.getCurrentPage() >= recentsView.getTaskViewStartIndex()) { if (recentsView.getCurrentPage() <= taskCount - 1) { dummyTask = recentsView.getCurrentPageTaskView(); } else { @@ -98,7 +98,7 @@ public class BackgroundAppState extends OverviewState { if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) { // Translate hotseat offscreen if we show it in overview. ScaleAndTranslation scaleAndTranslation = super.getHotseatScaleAndTranslation(launcher); - scaleAndTranslation.translationY = LayoutUtils.getShelfTrackingDistance(launcher, + scaleAndTranslation.translationY += LayoutUtils.getShelfTrackingDistance(launcher, launcher.getDeviceProfile()); return scaleAndTranslation; } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java index 93d4de17de..25eaab1879 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java @@ -32,7 +32,6 @@ import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7; import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE; -import android.content.Context; import android.graphics.Rect; import android.view.View; @@ -47,6 +46,7 @@ import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.quickstep.SysUINavigationMode; +import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; @@ -91,8 +91,19 @@ public class OverviewState extends LauncherState { @Override public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) { if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) { - // If the hotseat icons are visible in overview, keep them in their normal position. - return super.getWorkspaceScaleAndTranslation(launcher); + DeviceProfile dp = launcher.getDeviceProfile(); + if (dp.allAppsIconSizePx >= dp.iconSizePx) { + return new ScaleAndTranslation(1, 0, 0); + } else { + float scale = ((float) dp.allAppsIconSizePx) / dp.iconSizePx; + // Distance between the screen center (which is the pivotY for hotseat) and the + // bottom of the hotseat (which we want to preserve) + float distanceFromBottom = dp.heightPx / 2 - dp.hotseatBarBottomPaddingPx; + // On scaling, the bottom edge is moved closer to the pivotY. We move the + // hotseat back down so that the bottom edge's position is preserved. + float translationY = distanceFromBottom * (1 - scale); + return new ScaleAndTranslation(scale, 0, translationY); + } } return getWorkspaceScaleAndTranslation(launcher); } @@ -160,15 +171,7 @@ public class OverviewState extends LauncherState { } public static float getDefaultSwipeHeight(Launcher launcher) { - return getDefaultSwipeHeight(launcher, launcher.getDeviceProfile()); - } - - public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) { - float swipeHeight = dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx; - if (SysUINavigationMode.getMode(context) == SysUINavigationMode.Mode.NO_BUTTON) { - swipeHeight -= dp.getInsets().bottom; - } - return swipeHeight; + return LayoutUtils.getDefaultSwipeHeight(launcher, launcher.getDeviceProfile()); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java index 6c9f46fc47..7b4bb0274a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java @@ -20,13 +20,14 @@ import android.os.Looper; import com.android.launcher3.Launcher; import com.android.launcher3.userevent.nano.LauncherLogProto; +import com.android.quickstep.GestureState; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; /** * State to indicate we are about to launch a recent task. Note that this state is only used when - * quick switching from launcher; quick switching from an app uses WindowTransformSwipeHelper. - * @see com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget#NEW_TASK + * quick switching from launcher; quick switching from an app uses LauncherSwipeHandler. + * @see GestureState.GestureEndTarget#NEW_TASK */ public class QuickSwitchState extends BackgroundAppState { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java index ee2e9519e5..626292e867 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java @@ -35,12 +35,12 @@ import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL_3; import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; +import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; -import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; @@ -51,6 +51,7 @@ import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.quickstep.SystemUiProxy; +import com.android.launcher3.util.VibratorWrapper; import com.android.quickstep.util.MotionPauseDetector; import com.android.quickstep.views.RecentsView; @@ -106,8 +107,7 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { } }); mPeekAnim.start(); - recentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC); mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(isPaused ? 0 : 1, peekDuration, 0); @@ -173,7 +173,7 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { } @Override - public void onDragEnd(float velocity, boolean fling) { + public void onDragEnd(float velocity) { if (mMotionPauseDetector.isPaused() && handlingOverviewAnim()) { if (mPeekAnim != null) { mPeekAnim.cancel(); @@ -196,7 +196,7 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { }); overviewAnim.start(); } else { - super.onDragEnd(velocity, fling); + super.onDragEnd(velocity); } View searchView = mLauncher.getAppsView().getSearchView(); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java index d66af1ae24..ad4a343cb4 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java @@ -22,7 +22,9 @@ import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; import static com.android.launcher3.anim.Interpolators.DEACCEL_3; +import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS; +import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import android.animation.Animator; import android.animation.AnimatorSet; @@ -43,21 +45,25 @@ import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.compat.AccessibilityManagerCompat; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.util.TouchController; +import com.android.quickstep.util.AssistantUtilities; import com.android.quickstep.views.RecentsView; +import com.android.systemui.shared.system.ActivityManagerWrapper; /** * Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps. */ -public class NavBarToHomeTouchController implements TouchController, SwipeDetector.Listener { +public class NavBarToHomeTouchController implements TouchController, + SingleAxisSwipeDetector.Listener { private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3; private final Launcher mLauncher; - private final SwipeDetector mSwipeDetector; + private final SingleAxisSwipeDetector mSwipeDetector; private final float mPullbackDistance; private boolean mNoIntercept; @@ -67,7 +73,8 @@ public class NavBarToHomeTouchController implements TouchController, SwipeDetect public NavBarToHomeTouchController(Launcher launcher) { mLauncher = launcher; - mSwipeDetector = new SwipeDetector(mLauncher, this, SwipeDetector.VERTICAL); + mSwipeDetector = new SingleAxisSwipeDetector(mLauncher, this, + SingleAxisSwipeDetector.VERTICAL); mPullbackDistance = mLauncher.getResources().getDimension(R.dimen.home_pullback_distance); } @@ -79,7 +86,8 @@ public class NavBarToHomeTouchController implements TouchController, SwipeDetect if (mNoIntercept) { return false; } - mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false); + mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_POSITIVE, + false /* ignoreSlop */); } if (mNoIntercept) { @@ -101,6 +109,10 @@ public class NavBarToHomeTouchController implements TouchController, SwipeDetect if (AbstractFloatingView.getTopOpenView(mLauncher) != null) { return true; } + if (FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get() + && AssistantUtilities.isExcludedAssistantRunning()) { + return true; + } return false; } @@ -127,8 +139,13 @@ public class NavBarToHomeTouchController implements TouchController, SwipeDetect if (!recentsView.isRtl()) { pullbackDist = -pullbackDist; } - Animator pullback = ObjectAnimator.ofFloat(recentsView, TRANSLATION_X, pullbackDist); + ObjectAnimator pullback = ObjectAnimator.ofFloat(recentsView, TRANSLATION_X, + pullbackDist); pullback.setInterpolator(PULLBACK_INTERPOLATOR); + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + pullback.addUpdateListener( + valueAnimator -> recentsView.redrawLiveTile(false /* mightNeedToRefill */)); + } anim.play(pullback); } else if (mStartState == ALL_APPS) { AnimatorSetBuilder builder = new AnimatorSetBuilder(); @@ -173,13 +190,19 @@ public class NavBarToHomeTouchController implements TouchController, SwipeDetect } @Override - public void onDragEnd(float velocity, boolean fling) { + public void onDragEnd(float velocity) { + boolean fling = mSwipeDetector.isFling(velocity); final int logAction = fling ? Touch.FLING : Touch.SWIPE; float progress = mCurrentAnimation.getProgressFraction(); float interpolatedProgress = PULLBACK_INTERPOLATOR.getInterpolation(progress); boolean success = interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS || (velocity < 0 && fling); if (success) { + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + RecentsView recentsView = mLauncher.getOverviewPanel(); + recentsView.switchToScreenshot(null, + () -> recentsView.finishRecentsAnimation(true /* toRecents */, null)); + } mLauncher.getStateManager().goToState(mEndState, true, () -> onSwipeInteractionCompleted(mEndState)); if (mStartState != mEndState) { @@ -190,6 +213,8 @@ public class NavBarToHomeTouchController implements TouchController, SwipeDetect AbstractFloatingView.closeAllOpenViews(mLauncher); logStateChange(topOpenView.getLogContainerType(), logAction); } + ActivityManagerWrapper.getInstance() + .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); } else { // Quickly return to the state we came from (we didn't move far). ValueAnimator anim = mCurrentAnimation.getAnimationPlayer(); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java index 5c3b55d6c4..912be983f7 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java @@ -30,6 +30,7 @@ import static com.android.launcher3.anim.Interpolators.INSTANT; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD; +import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import android.view.MotionEvent; @@ -42,7 +43,7 @@ import com.android.launcher3.LauncherStateManager; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.touch.AbstractStateChangeTouchController; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.quickstep.SysUINavigationMode; @@ -50,6 +51,7 @@ import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; +import com.android.systemui.shared.system.ActivityManagerWrapper; /** * Handles quick switching to a recent task from the home screen. @@ -59,10 +61,10 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll private @Nullable TaskView mTaskToLaunch; public QuickSwitchTouchController(Launcher launcher) { - this(launcher, SwipeDetector.HORIZONTAL); + this(launcher, SingleAxisSwipeDetector.HORIZONTAL); } - protected QuickSwitchTouchController(Launcher l, SwipeDetector.Direction dir) { + protected QuickSwitchTouchController(Launcher l, SingleAxisSwipeDetector.Direction dir) { super(l, dir); } @@ -94,6 +96,8 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll super.onDragStart(start); mStartContainerType = LauncherLogProto.ContainerType.NAVBAR; mTaskToLaunch = mLauncher.getOverviewPanel().getTaskViewAt(0); + ActivityManagerWrapper.getInstance() + .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java index 00e4f58e92..ad02de1091 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java @@ -19,6 +19,9 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE; import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; +import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH; +import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE; +import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE; import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; import android.animation.Animator; @@ -32,7 +35,8 @@ import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.touch.BaseSwipeDetector; +import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.util.FlingBlockCheck; import com.android.launcher3.util.PendingAnimation; @@ -46,15 +50,14 @@ import com.android.quickstep.views.TaskView; * Touch controller for handling task view card swipes */ public abstract class TaskViewTouchController - extends AnimatorListenerAdapter implements TouchController, SwipeDetector.Listener { - - private static final String TAG = "OverviewSwipeController"; + extends AnimatorListenerAdapter implements TouchController, + SingleAxisSwipeDetector.Listener { // Progress after which the transition is assumed to be a success in case user does not fling public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f; protected final T mActivity; - private final SwipeDetector mDetector; + private final SingleAxisSwipeDetector mDetector; private final RecentsView mRecentsView; private final int[] mTempCords = new int[2]; @@ -74,7 +77,7 @@ public abstract class TaskViewTouchController public TaskViewTouchController(T activity) { mActivity = activity; mRecentsView = activity.getOverviewPanel(); - mDetector = new SwipeDetector(activity, this, SwipeDetector.VERTICAL); + mDetector = new SingleAxisSwipeDetector(activity, this, SingleAxisSwipeDetector.VERTICAL); } private boolean canInterceptTouch() { @@ -113,7 +116,7 @@ public abstract class TaskViewTouchController int directionsToDetectScroll = 0; boolean ignoreSlopWhenSettling = false; if (mCurrentAnimation != null) { - directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH; + directionsToDetectScroll = DIRECTION_BOTH; ignoreSlopWhenSettling = true; } else { mTaskBeingDragged = null; @@ -126,12 +129,12 @@ public abstract class TaskViewTouchController if (!SysUINavigationMode.getMode(mActivity).hasGestures) { // Don't allow swipe down to open if we don't support swipe up // to enter overview. - directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE; + directionsToDetectScroll = DIRECTION_POSITIVE; } else { // The task can be dragged up to dismiss it, // and down to open if it's the current page. directionsToDetectScroll = i == mRecentsView.getCurrentPage() - ? SwipeDetector.DIRECTION_BOTH : SwipeDetector.DIRECTION_POSITIVE; + ? DIRECTION_BOTH : DIRECTION_POSITIVE; } break; } @@ -165,8 +168,8 @@ public abstract class TaskViewTouchController return; } int scrollDirections = mDetector.getScrollDirections(); - if (goingUp && ((scrollDirections & SwipeDetector.DIRECTION_POSITIVE) == 0) - || !goingUp && ((scrollDirections & SwipeDetector.DIRECTION_NEGATIVE) == 0)) { + if (goingUp && ((scrollDirections & DIRECTION_POSITIVE) == 0) + || !goingUp && ((scrollDirections & DIRECTION_NEGATIVE) == 0)) { // Trying to re-init in an unsupported direction. return; } @@ -243,7 +246,8 @@ public abstract class TaskViewTouchController } @Override - public void onDragEnd(float velocity, boolean fling) { + public void onDragEnd(float velocity) { + boolean fling = mDetector.isFling(velocity); final boolean goingToEnd; final int logAction; boolean blockedFling = fling && mFlingBlockCheck.isBlocked(); @@ -260,7 +264,7 @@ public abstract class TaskViewTouchController logAction = Touch.SWIPE; goingToEnd = interpolatedProgress > SUCCESS_TRANSITION_PROGRESS; } - long animationDuration = SwipeDetector.calculateDuration( + long animationDuration = BaseSwipeDetector.calculateDuration( velocity, goingToEnd ? (1 - progress) : progress); if (blockedFling && !goingToEnd) { animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java index f1e4041eb2..0ed529184e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java @@ -17,12 +17,12 @@ package com.android.launcher3.uioverrides.touchcontrollers; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.touch.SingleAxisSwipeDetector; public class TransposedQuickSwitchTouchController extends QuickSwitchTouchController { public TransposedQuickSwitchTouchController(Launcher launcher) { - super(launcher, SwipeDetector.VERTICAL); + super(launcher, SingleAxisSwipeDetector.VERTICAL); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java index 8a11ac80aa..59b117f2e8 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -70,7 +70,7 @@ final class AppToOverviewAnimationProvider imple activity.getOverviewPanel().showCurrentTask(mTargetTaskId); AbstractFloatingView.closeAllOpenViews(activity, wasVisible); BaseActivityInterface.AnimationFactory factory = - mHelper.prepareRecentsUI(activity, wasVisible, + mHelper.prepareRecentsUI(wasVisible, false /* animate activity */, (controller) -> { controller.dispatchOnStart(); ValueAnimator anim = controller.getAnimationPlayer() @@ -102,7 +102,7 @@ final class AppToOverviewAnimationProvider imple anim.addListener(new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animator) { - mHelper.onSwipeUpToRecentsComplete(mActivity); + mHelper.onSwipeUpToRecentsComplete(); if (mRecentsView != null) { mRecentsView.animateUpRunningTaskIconScale(); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java index e1e994c6a6..b14da5c8d8 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java @@ -15,20 +15,15 @@ */ package com.android.quickstep; -import static android.os.VibrationEffect.EFFECT_CLICK; -import static android.os.VibrationEffect.createPredefined; - -import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.anim.Interpolators.ACCEL_1_5; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; import android.animation.Animator; import android.annotation.TargetApi; -import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.content.Intent; import android.graphics.Point; @@ -36,11 +31,6 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.os.VibrationEffect; -import android.os.Vibrator; -import android.provider.Settings; import android.view.MotionEvent; import android.view.View; import android.view.animation.Interpolator; @@ -55,15 +45,15 @@ import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.graphics.RotationMode; +import com.android.launcher3.util.VibratorWrapper; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory; -import com.android.quickstep.SysUINavigationMode.Mode; +import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.AppWindowAnimationHelper; import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams; import com.android.quickstep.util.RectFSpringAnim; -import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -96,17 +86,14 @@ public abstract class BaseSwipeUpHandler mActivityInterface; - protected final RecentsModel mRecentsModel; - protected final int mRunningTaskId; + protected final InputConsumerController mInputConsumer; protected final AppWindowAnimationHelper mAppWindowAnimationHelper; protected final TransformParams mTransformParams = new TransformParams(); - private final Vibrator mVibrator; - protected final Mode mMode; - // Shift in the range of [0, 1]. // 0 => preview snapShot is completely visible, and hotseat is completely translated down // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely @@ -114,7 +101,6 @@ public abstract class BaseSwipeUpHandler mStateCallback.setState(stateFlag)); - } - } - protected void performHapticFeedback() { - if (!mVibrator.hasVibrator()) { - return; - } - if (Settings.System.getInt( - mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) == 0) { - return; - } - - VibrationEffect effect = createPredefined(EFFECT_CLICK); - if (effect == null) { - return; - } - UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(effect)); + VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC); } public Consumer getRecentsViewDispatcher(RotationMode rotationMode) { @@ -246,14 +208,14 @@ public abstract class BaseSwipeUpHandler { resultCallback.accept(success); if (!success) { - mActivityInterface.onLaunchTaskFailed(mActivity); + mActivityInterface.onLaunchTaskFailed(); nextTask.notifyTaskLaunchFailed(TAG); } else { - mActivityInterface.onLaunchTaskSuccess(mActivity); + mActivityInterface.onLaunchTaskSuccess(); } - }, mMainThreadHandler); + }, MAIN_EXECUTOR.getHandler()); } - setStateOnUiThread(successStateFlag); + mStateCallback.setStateOnUiThread(successStateFlag); } mCanceled = false; mFinishingRecentsAnimationForNewTaskId = -1; @@ -288,7 +250,8 @@ public abstract class BaseSwipeUpHandler callback) { + RecentsActivity activity = getCreatedActivity(); if (activityVisible) { return (transitionLength) -> { }; } @@ -176,8 +183,9 @@ public final class FallbackActivityInterface implements @Override public ActivityInitListener createActivityInitListener( - BiPredicate onInitListener) { - return new ActivityInitListener(onInitListener, RecentsActivity.ACTIVITY_TRACKER); + Predicate onInitListener) { + return new ActivityInitListener<>((activity, alreadyOnHome) -> + onInitListener.test(alreadyOnHome), RecentsActivity.ACTIVITY_TRACKER); } @Nullable @@ -228,13 +236,21 @@ public final class FallbackActivityInterface implements } @Override - public void onLaunchTaskFailed(RecentsActivity activity) { + public void onLaunchTaskFailed() { // TODO: probably go back to overview instead. + RecentsActivity activity = getCreatedActivity(); + if (activity == null) { + return; + } activity.getOverviewPanel().startHome(); } @Override - public void onLaunchTaskSuccess(RecentsActivity activity) { + public void onLaunchTaskSuccess() { + RecentsActivity activity = getCreatedActivity(); + if (activity == null) { + return; + } activity.onTaskLaunched(); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java similarity index 73% rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java rename to quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java index 370b48793c..c939de88f5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java @@ -13,54 +13,46 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.quickstep.inputconsumers; +package com.android.quickstep; +import static com.android.quickstep.GestureState.GestureEndTarget.HOME; +import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK; +import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK; +import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS; +import static com.android.quickstep.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW; import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID; import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL; -import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW; -import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.HOME; -import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.LAST_TASK; -import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.NEW_TASK; -import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.RECENTS; import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD; import android.animation.Animator; import android.animation.AnimatorSet; -import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; import android.graphics.PointF; import android.graphics.RectF; import android.os.Bundle; +import android.util.ArrayMap; import com.android.launcher3.R; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.util.ObjectWrapper; import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory; -import com.android.quickstep.AnimatedFloat; -import com.android.quickstep.BaseSwipeUpHandler; -import com.android.quickstep.GestureState; -import com.android.quickstep.InputConsumer; -import com.android.quickstep.MultiStateCallback; -import com.android.quickstep.OverviewComponentObserver; -import com.android.quickstep.RecentsActivity; -import com.android.quickstep.RecentsAnimationController; -import com.android.quickstep.RecentsModel; -import com.android.quickstep.SwipeSharedState; +import com.android.quickstep.GestureState.GestureEndTarget; import com.android.quickstep.fallback.FallbackRecentsView; import com.android.quickstep.util.RectFSpringAnim; -import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.ActivityOptionsCompat; import com.android.systemui.shared.system.InputConsumerController; -public class FallbackNoButtonInputConsumer extends - BaseSwipeUpHandler { +/** + * Handles the navigation gestures when a 3rd party launcher is the default home activity. + */ +public class FallbackSwipeHandler extends BaseSwipeUpHandler { private static final String[] STATE_NAMES = DEBUG_STATES ? new String[5] : null; @@ -83,51 +75,41 @@ public class FallbackNoButtonInputConsumer extends private static final int STATE_APP_CONTROLLER_RECEIVED = getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED"); - public enum GestureEndTarget { - HOME(3, 100, 1), - RECENTS(1, 300, 0), - LAST_TASK(0, 150, 1), - NEW_TASK(0, 150, 1); - + public static class EndTargetAnimationParams { private final float mEndProgress; private final long mDurationMultiplier; private final float mLauncherAlpha; - GestureEndTarget(float endProgress, long durationMultiplier, float launcherAlpha) { + EndTargetAnimationParams(float endProgress, long durationMultiplier, float launcherAlpha) { mEndProgress = endProgress; mDurationMultiplier = durationMultiplier; mLauncherAlpha = launcherAlpha; } } + private static ArrayMap + mEndTargetAnimationParams = new ArrayMap(); private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged); private boolean mIsMotionPaused = false; - private GestureEndTarget mEndTarget; private final boolean mInQuickSwitchMode; private final boolean mContinuingLastGesture; private final boolean mRunningOverHome; private final boolean mSwipeUpOverHome; - private final RunningTaskInfo mRunningTaskInfo; - private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f); private RunningWindowAnim mFinishAnimation; - public FallbackNoButtonInputConsumer(Context context, GestureState gestureState, - OverviewComponentObserver overviewComponentObserver, - RunningTaskInfo runningTaskInfo, RecentsModel recentsModel, - InputConsumerController inputConsumer, + public FallbackSwipeHandler(Context context, RecentsAnimationDeviceState deviceState, + GestureState gestureState, InputConsumerController inputConsumer, boolean isLikelyToStartNewTask, boolean continuingLastGesture) { - super(context, gestureState, overviewComponentObserver, recentsModel, inputConsumer, - runningTaskInfo.id); + super(context, deviceState, gestureState, inputConsumer); mLauncherAlpha.value = 1; - mRunningTaskInfo = runningTaskInfo; mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture; mContinuingLastGesture = continuingLastGesture; - mRunningOverHome = ActivityManagerWrapper.isHomeTask(runningTaskInfo); + mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask()); mSwipeUpOverHome = mRunningOverHome && !mInQuickSwitchMode; if (mSwipeUpOverHome) { @@ -136,40 +118,46 @@ public class FallbackNoButtonInputConsumer extends mAppWindowAnimationHelper.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value); } + // Going home has an extra long progress to ensure that it animates into the screen + mEndTargetAnimationParams.put(HOME, new EndTargetAnimationParams(3, 100, 1)); + mEndTargetAnimationParams.put(RECENTS, new EndTargetAnimationParams(1, 300, 0)); + mEndTargetAnimationParams.put(LAST_TASK, new EndTargetAnimationParams(0, 150, 1)); + mEndTargetAnimationParams.put(NEW_TASK, new EndTargetAnimationParams(0, 150, 1)); + initStateCallbacks(); } private void initStateCallbacks() { mStateCallback = new MultiStateCallback(STATE_NAMES); - mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, + mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::onHandlerInvalidated); - mStateCallback.addCallback(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED, + mStateCallback.runOnceAtState(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED, this::onHandlerInvalidatedWithRecents); - mStateCallback.addCallback(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED, + mStateCallback.runOnceAtState(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED, this::finishAnimationTargetSetAnimationComplete); if (mInQuickSwitchMode) { - mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED + mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED | STATE_RECENTS_PRESENT, this::finishAnimationTargetSet); } else { - mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED, + mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED, this::finishAnimationTargetSet); } } private void onLauncherAlphaChanged() { - if (mRecentsAnimationTargets != null && mEndTarget == null) { + if (mRecentsAnimationTargets != null && mGestureState.getEndTarget() == null) { applyTransformUnchecked(); } } @Override - protected boolean onActivityInit(final RecentsActivity activity, Boolean alreadyOnHome) { - mActivity = activity; - mRecentsView = activity.getOverviewPanel(); + protected boolean onActivityInit(Boolean alreadyOnHome) { + mActivity = mActivityInterface.getCreatedActivity(); + mRecentsView = mActivity.getOverviewPanel(); linkRecentsViewScroll(); mRecentsView.setDisallowScrollToClearAll(true); mRecentsView.getClearAllButton().setVisibilityAlpha(0); @@ -178,12 +166,12 @@ public class FallbackNoButtonInputConsumer extends if (!mContinuingLastGesture) { if (mRunningOverHome) { - mRecentsView.onGestureAnimationStart(mRunningTaskInfo); + mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask()); } else { - mRecentsView.onGestureAnimationStart(mRunningTaskId); + mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId()); } } - setStateOnUiThread(STATE_RECENTS_PRESENT); + mStateCallback.setStateOnUiThread(STATE_RECENTS_PRESENT); return true; } @@ -226,9 +214,9 @@ public class FallbackNoButtonInputConsumer extends @Override public Intent getLaunchIntent() { if (mInQuickSwitchMode || mSwipeUpOverHome) { - return mOverviewComponentObserver.getOverviewIntent(); + return mGestureState.getOverviewIntent(); } else { - return mOverviewComponentObserver.getHomeIntent(); + return mGestureState.getHomeIntent(); } } @@ -247,8 +235,8 @@ public class FallbackNoButtonInputConsumer extends @Override public void onGestureCancelled() { updateDisplacement(0); - mEndTarget = LAST_TASK; - setStateOnUiThread(STATE_GESTURE_CANCELLED); + mGestureState.setEndTarget(LAST_TASK); + mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED); } @Override @@ -256,28 +244,29 @@ public class FallbackNoButtonInputConsumer extends mEndVelocityPxPerMs.set(0, velocity.y / 1000); if (mInQuickSwitchMode) { // For now set it to non-null, it will be reset before starting the animation - mEndTarget = LAST_TASK; + mGestureState.setEndTarget(LAST_TASK); } else { float flingThreshold = mContext.getResources() .getDimension(R.dimen.quickstep_fling_threshold_velocity); boolean isFling = Math.abs(endVelocity) > flingThreshold; if (isFling) { - mEndTarget = endVelocity < 0 ? HOME : LAST_TASK; + mGestureState.setEndTarget(endVelocity < 0 ? HOME : LAST_TASK); } else if (mIsMotionPaused) { - mEndTarget = RECENTS; + mGestureState.setEndTarget(RECENTS); } else { - mEndTarget = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? HOME : LAST_TASK; + mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW + ? HOME + : LAST_TASK); } } - setStateOnUiThread(STATE_GESTURE_COMPLETED); + mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED); } @Override - public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) { - if (mInQuickSwitchMode && mEndTarget != null) { - sharedState.canGestureBeContinued = true; - sharedState.goingToLauncher = false; + public void onConsumerAboutToBeSwitched() { + if (mInQuickSwitchMode && mGestureState.getEndTarget() != null) { + mGestureState.setEndTarget(HOME); mCanceled = true; mCurrentShift.cancelAnimation(); @@ -293,12 +282,12 @@ public class FallbackNoButtonInputConsumer extends ? newRunningTaskView.getTask().key.id : -1; mRecentsView.setCurrentTask(newRunningTaskId); - sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId); + mGestureState.setFinishingRecentsAnimationTaskId(newRunningTaskId); } mRecentsView.setOnScrollChangeListener(null); } } else { - setStateOnUiThread(STATE_HANDLER_INVALIDATED); + mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED); } } @@ -319,12 +308,12 @@ public class FallbackNoButtonInputConsumer extends } private void finishAnimationTargetSetAnimationComplete() { - switch (mEndTarget) { + switch (mGestureState.getEndTarget()) { case HOME: { if (mSwipeUpOverHome) { mRecentsAnimationController.finish(false, null, false); // Send a home intent to clear the task stack - mContext.startActivity(mOverviewComponentObserver.getHomeIntent()); + mContext.startActivity(mGestureState.getHomeIntent()); } else { mRecentsAnimationController.finish(true, null, true); } @@ -339,7 +328,8 @@ public class FallbackNoButtonInputConsumer extends break; } - ThumbnailData thumbnail = mRecentsAnimationController.screenshotTask(mRunningTaskId); + final int runningTaskId = mGestureState.getRunningTaskId(); + ThumbnailData thumbnail = mRecentsAnimationController.screenshotTask(runningTaskId); mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */, false /* screenshot */); @@ -348,9 +338,9 @@ public class FallbackNoButtonInputConsumer extends Bundle extras = new Bundle(); extras.putBinder(EXTRA_THUMBNAIL, new ObjectWrapper<>(thumbnail)); - extras.putInt(EXTRA_TASK_ID, mRunningTaskId); + extras.putInt(EXTRA_TASK_ID, runningTaskId); - Intent intent = new Intent(mOverviewComponentObserver.getOverviewIntent()) + Intent intent = new Intent(mGestureState.getOverviewIntent()) .putExtras(extras); mContext.startActivity(intent, options.toBundle()); mRecentsAnimationController.cleanupScreenshot(); @@ -362,7 +352,7 @@ public class FallbackNoButtonInputConsumer extends } } - setStateOnUiThread(STATE_HANDLER_INVALIDATED); + mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED); } private void finishAnimationTargetSet() { @@ -370,17 +360,20 @@ public class FallbackNoButtonInputConsumer extends // Recalculate the end target, some views might have been initialized after // gesture has ended. if (mRecentsView == null || !hasTargets()) { - mEndTarget = LAST_TASK; + mGestureState.setEndTarget(LAST_TASK); } else { final int runningTaskIndex = mRecentsView.getRunningTaskIndex(); final int taskToLaunch = mRecentsView.getNextPage(); - mEndTarget = (runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex) - ? NEW_TASK : LAST_TASK; + mGestureState.setEndTarget( + (runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex) + ? NEW_TASK + : LAST_TASK); } } - float endProgress = mEndTarget.mEndProgress; - long duration = (long) (mEndTarget.mDurationMultiplier * + EndTargetAnimationParams params = mEndTargetAnimationParams.get(mGestureState.getEndTarget()); + float endProgress = params.mEndProgress; + long duration = (long) (params.mDurationMultiplier * Math.abs(endProgress - mCurrentShift.value)); if (mRecentsView != null) { duration = Math.max(duration, mRecentsView.getScroller().getDuration()); @@ -395,7 +388,7 @@ public class FallbackNoButtonInputConsumer extends } }; - if (mEndTarget == HOME && !mRunningOverHome) { + if (mGestureState.getEndTarget() == HOME && !mRunningOverHome) { RectFSpringAnim anim = createWindowAnimationToHome(mCurrentShift.value, duration); anim.addAnimatorListener(endListener); anim.start(mEndVelocityPxPerMs); @@ -404,7 +397,7 @@ public class FallbackNoButtonInputConsumer extends AnimatorSet anim = new AnimatorSet(); anim.play(mLauncherAlpha.animateToValue( - mLauncherAlpha.value, mEndTarget.mLauncherAlpha)); + mLauncherAlpha.value, params.mLauncherAlpha)); anim.play(mCurrentShift.animateToValue(mCurrentShift.value, endProgress)); anim.setDuration(duration); @@ -429,13 +422,13 @@ public class FallbackNoButtonInputConsumer extends } applyTransformUnchecked(); - setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED); + mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED); } @Override public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) { - mRecentsView.setRecentsAnimationTargets(null, null); - setStateOnUiThread(STATE_HANDLER_INVALIDATED); + super.onRecentsAnimationCanceled(thumbnailData); + mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED); } /** diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java index f6b3654e69..844152be28 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java @@ -29,7 +29,7 @@ import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; import static com.android.launcher3.anim.Interpolators.INSTANT; import static com.android.launcher3.anim.Interpolators.LINEAR; -import static com.android.quickstep.WindowTransformSwipeHandler.RECENTS_ATTACH_DURATION; +import static com.android.quickstep.LauncherSwipeHandler.RECENTS_ATTACH_DURATION; import android.animation.Animator; import android.animation.AnimatorSet; @@ -47,20 +47,23 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; +import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherInitListenerEx; +import com.android.launcher3.LauncherInitListener; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorSetBuilder; -import com.android.launcher3.uioverrides.states.OverviewState; +import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.LayoutUtils; +import com.android.quickstep.util.ShelfPeekAnim; +import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState; import com.android.quickstep.util.StaggeredWorkspaceAnim; import com.android.quickstep.views.LauncherRecentsView; import com.android.quickstep.views.RecentsView; @@ -69,8 +72,8 @@ import com.android.systemui.plugins.shared.LauncherOverlayManager; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; -import java.util.function.BiPredicate; import java.util.function.Consumer; +import java.util.function.Predicate; /** * {@link BaseActivityInterface} for the in-launcher recents. @@ -92,50 +95,65 @@ public final class LauncherActivityInterface implements BaseActivityInterface callback) { - final LauncherState startState = activity.getStateManager().getState(); + BaseQuickstepLauncher launcher = getCreatedActivity(); + final LauncherState startState = launcher.getStateManager().getState(); LauncherState resetState = startState; if (startState.disableRestore) { - resetState = activity.getStateManager().getRestState(); + resetState = launcher.getStateManager().getRestState(); } - activity.getStateManager().setRestState(resetState); + launcher.getStateManager().setRestState(resetState); final LauncherState fromState = animateActivity ? BACKGROUND_APP : OVERVIEW; - activity.getStateManager().goToState(fromState, false); + launcher.getStateManager().goToState(fromState, false); // Since all apps is not visible, we can safely reset the scroll position. // This ensures then the next swipe up to all-apps starts from scroll 0. - activity.getAppsView().reset(false /* animate */); + launcher.getAppsView().reset(false /* animate */); return new AnimationFactory() { - private ShelfAnimState mShelfState; + private final ShelfPeekAnim mShelfAnim = launcher.getShelfPeekAnim(); private boolean mIsAttachedToWindow; @Override public void createActivityInterface(long transitionLength) { - createActivityInterfaceInternal(activity, fromState, transitionLength, callback); + createActivityInterfaceInternal(launcher, fromState, transitionLength, callback); // Creating the activity controller animation sometimes reapplies the launcher state // (because we set the animation as the current state animation), so we reapply the // attached state here as well to ensure recents is shown/hidden appropriately. - if (SysUINavigationMode.getMode(activity) == Mode.NO_BUTTON) { + if (SysUINavigationMode.getMode(launcher) == Mode.NO_BUTTON) { setRecentsAttachedToAppWindow(mIsAttachedToWindow, false); } } @@ -233,36 +252,13 @@ public final class LauncherActivityInterface implements BaseActivityInterface onInitListener) { - return new LauncherInitListenerEx(onInitListener); + public ActivityInitListener createActivityInitListener(Predicate onInitListener) { + return new LauncherInitListener((activity, alreadyOnHome) -> + onInitListener.test(alreadyOnHome)); } @Nullable @Override - public Launcher getCreatedActivity() { - return Launcher.ACTIVITY_TRACKER.getCreatedActivity(); + public BaseQuickstepLauncher getCreatedActivity() { + return BaseQuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity(); } @Nullable @@ -469,12 +465,20 @@ public final class LauncherActivityInterface implements BaseActivityInterface +public class LauncherSwipeHandler extends BaseSwipeUpHandler implements OnApplyWindowInsetsListener { - private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName(); + private static final String TAG = LauncherSwipeHandler.class.getSimpleName(); private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null; @@ -138,42 +144,6 @@ public class WindowTransformSwipeHandler private static final int LAUNCHER_UI_STATES = STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED; - public enum GestureEndTarget { - HOME(1, STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT, true, false, - ContainerType.WORKSPACE, false), - - RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT - | STATE_SCREENSHOT_VIEW_SHOWN, true, false, ContainerType.TASKSWITCHER, true), - - NEW_TASK(0, STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT, false, true, - ContainerType.APP, true), - - LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP, false); - - GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued, - int containerType, boolean recentsAttachedToAppWindow) { - this.endShift = endShift; - this.endState = endState; - this.isLauncher = isLauncher; - this.canBeContinued = canBeContinued; - this.containerType = containerType; - this.recentsAttachedToAppWindow = recentsAttachedToAppWindow; - } - - /** 0 is app, 1 is overview */ - public final float endShift; - /** The state to apply when we reach this final target */ - public final int endState; - /** Whether the target is in the launcher activity */ - public final boolean isLauncher; - /** Whether the user can start a new gesture while this one is finishing */ - public final boolean canBeContinued; - /** Used to log where the user ended up after the gesture ends */ - public final int containerType; - /** Whether RecentsView should be attached to the window as we animate to this target */ - public final boolean recentsAttachedToAppWindow; - } - public static final long MAX_SWIPE_DURATION = 350; public static final long MIN_SWIPE_DURATION = 80; public static final long MIN_OVERSHOOT_DURATION = 120; @@ -183,7 +153,6 @@ public class WindowTransformSwipeHandler Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW)); private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured"; - private static final long SHELF_ANIM_DURATION = 240; public static final long RECENTS_ATTACH_DURATION = 300; /** @@ -191,10 +160,8 @@ public class WindowTransformSwipeHandler */ private static final int LOG_NO_OP_PAGE_INDEX = -1; - private final RecentsAnimationDeviceState mDeviceState; - private final GestureState mGestureState; + private final TaskAnimationManager mTaskAnimationManager; - private GestureEndTarget mGestureEndTarget; // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise private RunningWindowAnim mRunningWindowAnim; private boolean mIsShelfPeeking; @@ -210,8 +177,6 @@ public class WindowTransformSwipeHandler private boolean mHasLauncherTransitionControllerStarted; private AnimationFactory mAnimationFactory = (t) -> { }; - private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay(); - private boolean mLiveTileOverlayAttached = false; private boolean mWasLauncherAlreadyVisible; @@ -225,13 +190,14 @@ public class WindowTransformSwipeHandler private final long mTouchTimeMs; private long mLauncherFrameDrawnTime; - public WindowTransformSwipeHandler(Context context, RecentsAnimationDeviceState deviceState, - GestureState gestureState, RunningTaskInfo runningTaskInfo, long touchTimeMs, - OverviewComponentObserver overviewComponentObserver, boolean continuingLastGesture, - InputConsumerController inputConsumer, RecentsModel recentsModel) { - super(context, gestureState, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id); - mDeviceState = deviceState; - mGestureState = gestureState; + private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch; + + public LauncherSwipeHandler(Context context, RecentsAnimationDeviceState deviceState, + TaskAnimationManager taskAnimationManager, GestureState gestureState, + long touchTimeMs, boolean continuingLastGesture, + InputConsumerController inputConsumer) { + super(context, deviceState, gestureState, inputConsumer); + mTaskAnimationManager = taskAnimationManager; mTouchTimeMs = touchTimeMs; mContinuingLastGesture = continuingLastGesture; initStateCallbacks(); @@ -240,62 +206,65 @@ public class WindowTransformSwipeHandler private void initStateCallbacks() { mStateCallback = new MultiStateCallback(STATE_NAMES); - mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED, + mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED, this::onLauncherPresentAndGestureStarted); - mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED, + mStateCallback.runOnceAtState(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED, this::initializeLauncherAnimationController); - mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN, + mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN, this::launcherFrameDrawn); - mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED + mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED | STATE_GESTURE_CANCELLED, this::resetStateForAnimationCancel); - mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED, + mStateCallback.runOnceAtState(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED, this::sendRemoteAnimationsToAnimationFactory); - mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED, + mStateCallback.runOnceAtState(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED, this::resumeLastTask); - mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED, + mStateCallback.runOnceAtState(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED, this::startNewTask); - mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED + mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT, this::switchToScreenshot); - mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED + mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED | STATE_SCALED_CONTROLLER_RECENTS, this::finishCurrentTransitionToRecents); - mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED + mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED | STATE_SCALED_CONTROLLER_HOME, this::finishCurrentTransitionToHome); - mStateCallback.addCallback(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED, + mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED, this::reset); - mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED + mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED | STATE_GESTURE_STARTED, this::setupLauncherUiAfterSwipeUpToRecentsAnimation); - mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, this::invalidateHandler); - mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED, + mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED, this::onEndTargetSet); + + mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::invalidateHandler); + mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED, this::invalidateHandlerWithLauncher); - mStateCallback.addCallback(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK, + mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK, this::notifyTransitionCancelled); if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) { - mStateCallback.addChangeHandler(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT + mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT, (b) -> mRecentsView.setRunningTaskHidden(!b)); } } @Override - protected boolean onActivityInit(final T activity, Boolean alreadyOnHome) { + protected boolean onActivityInit(Boolean alreadyOnHome) { + final T activity = mActivityInterface.getCreatedActivity(); if (mActivity == activity) { return true; } @@ -321,21 +290,28 @@ public class WindowTransformSwipeHandler mStateCallback.setState(STATE_LAUNCHER_PRESENT); if (alreadyOnHome) { - onLauncherStart(activity); + onLauncherStart(); } else { - activity.setOnStartCallback(this::onLauncherStart); + activity.runOnceOnStart(this::onLauncherStart); } setupRecentsViewUi(); + + if (mDeviceState.getNavMode() == TWO_BUTTONS) { + // If the device is in two button mode, swiping up will show overview with predictions + // so we need to kick off switching to the overview predictions as soon as possible + mActivityInterface.updateOverviewPredictionState(); + } return true; } @Override protected boolean moveWindowWithRecentsScroll() { - return mGestureEndTarget != HOME; + return mGestureState.getEndTarget() != HOME; } - private void onLauncherStart(final T activity) { + private void onLauncherStart() { + final T activity = mActivityInterface.getCreatedActivity(); if (mActivity != activity) { return; } @@ -345,9 +321,9 @@ public class WindowTransformSwipeHandler // If we've already ended the gesture and are going home, don't prepare recents UI, // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL. - if (mGestureEndTarget != HOME) { + if (mGestureState.getEndTarget() != HOME) { Runnable initAnimFactory = () -> { - mAnimationFactory = mActivityInterface.prepareRecentsUI(mActivity, + mAnimationFactory = mActivityInterface.prepareRecentsUI( mWasLauncherAlreadyVisible, true, this::onAnimatorPlaybackControllerCreated); maybeUpdateRecentsAttachedState(false /* animate */); @@ -356,7 +332,7 @@ public class WindowTransformSwipeHandler // Launcher is visible, but might be about to stop. Thus, if we prepare recents // now, it might get overridden by moveToRestState() in onStop(). To avoid this, // wait until the next gesture (and possibly launcher) starts. - mStateCallback.addCallback(STATE_GESTURE_STARTED, initAnimFactory); + mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, initAnimFactory); } else { initAnimFactory.run(); } @@ -400,15 +376,31 @@ public class WindowTransformSwipeHandler // that time by a previous window transition. setupRecentsViewUi(); + // For the duration of the gesture, in cases where an activity is launched while the + // activity is not yet resumed, finish the animation to ensure we get resumed + mGestureState.getActivityInterface().setOnDeferredActivityLaunchCallback( + mOnDeferredActivityLaunch); + notifyGestureStartedAsync(); } + private void onDeferredActivityLaunch() { + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + mActivityInterface.switchRunningTaskViewToScreenshot( + null, () -> { + mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */); + }); + } else { + mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */); + } + } + private void setupRecentsViewUi() { if (mContinuingLastGesture) { updateSysUiFlags(mCurrentShift.value); return; } - mRecentsView.onGestureAnimationStart(mRunningTaskId); + mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId()); } private void launcherFrameDrawn() { @@ -437,16 +429,15 @@ public class WindowTransformSwipeHandler .getHighResLoadingState().setVisible(true); } - private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) { - float distanceToReachEdge = mDp.widthPx / 2 + taskWidth / 2 + - mContext.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing); - float interpolation = Math.min(1, offsetX / distanceToReachEdge); - return TaskView.getCurveScaleForInterpolation(interpolation); - } - @Override public void onMotionPauseChanged(boolean isPaused) { - setShelfState(isPaused ? PEEK : HIDE, OVERSHOOT_1_2, SHELF_ANIM_DURATION); + setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION); + + if (mDeviceState.isFullyGesturalNavMode() && isPaused) { + // In fully gestural nav mode, switch to overview predictions once the user has paused + // (this is a no-op if the predictions are already in that state) + mActivityInterface.updateOverviewPredictionState(); + } } public void maybeUpdateRecentsAttachedState() { @@ -461,16 +452,15 @@ public class WindowTransformSwipeHandler * Note this method has no effect unless the navigation mode is NO_BUTTON. */ private void maybeUpdateRecentsAttachedState(boolean animate) { - if (mMode != Mode.NO_BUTTON || mRecentsView == null) { + if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) { return; } - RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets == null - ? null - : mRecentsAnimationTargets.findTask(mRunningTaskId); + RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null + ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId()) + : null; final boolean recentsAttachedToAppWindow; - int runningTaskIndex = mRecentsView.getRunningTaskIndex(); - if (mGestureEndTarget != null) { - recentsAttachedToAppWindow = mGestureEndTarget.recentsAttachedToAppWindow; + if (mGestureState.getEndTarget() != null) { + recentsAttachedToAppWindow = mGestureState.getEndTarget().recentsAttachedToAppWindow; } else if (mContinuingLastGesture && mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) { recentsAttachedToAppWindow = true; @@ -517,9 +507,10 @@ public class WindowTransformSwipeHandler } private void buildAnimationController() { - if (mGestureEndTarget == HOME || mHasLauncherTransitionControllerStarted) { - // We don't want a new mLauncherTransitionController if mGestureEndTarget == HOME (it - // has its own animation) or if we're already animating the current controller. + if (mGestureState.getEndTarget() == HOME || mHasLauncherTransitionControllerStarted) { + // We don't want a new mLauncherTransitionController if + // mGestureState.getEndTarget() == HOME (it has its own animation) or if we're already + // animating the current controller. return; } initTransitionEndpoints(mActivity.getDeviceProfile()); @@ -543,7 +534,7 @@ public class WindowTransformSwipeHandler @Override public Intent getLaunchIntent() { - return mOverviewComponentObserver.getOverviewIntent(); + return mGestureState.getOverviewIntent(); } @Override @@ -555,7 +546,8 @@ public class WindowTransformSwipeHandler if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { if (mRecentsAnimationTargets != null) { - mLiveTileOverlay.update(mAppWindowAnimationHelper.getCurrentRectWithInsets(), + LiveTileOverlay.getInstance().update( + mAppWindowAnimationHelper.getCurrentRectWithInsets(), mAppWindowAnimationHelper.getCurrentCornerRadius()); } } @@ -563,7 +555,7 @@ public class WindowTransformSwipeHandler final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW; if (passed != mPassedOverviewThreshold) { mPassedOverviewThreshold = passed; - if (mMode != Mode.NO_BUTTON) { + if (!mDeviceState.isFullyGesturalNavMode()) { performHapticFeedback(); } } @@ -576,7 +568,7 @@ public class WindowTransformSwipeHandler } private void updateLauncherTransitionProgress() { - if (mGestureEndTarget == HOME) { + if (mGestureState.getEndTarget() == HOME) { return; } // Normalize the progress to 0 to 1, as the animation controller will clamp it to that @@ -613,9 +605,9 @@ public class WindowTransformSwipeHandler super.onRecentsAnimationStart(controller, targets); // Only add the callback to enable the input consumer after we actually have the controller - mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED, + mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED, mRecentsAnimationController::enableInputConsumer); - setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED); + mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED); mPassedOverviewThreshold = false; } @@ -623,9 +615,8 @@ public class WindowTransformSwipeHandler @Override public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) { super.onRecentsAnimationCanceled(thumbnailData); - mRecentsView.setRecentsAnimationTargets(null, null); mActivityInitListener.unregister(); - setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED); + mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED); ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation"); } @@ -633,7 +624,7 @@ public class WindowTransformSwipeHandler public void onGestureStarted() { notifyGestureStartedAsync(); mShiftAtGestureStart = mCurrentShift.value; - setStateOnUiThread(STATE_GESTURE_STARTED); + mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED); mGestureStarted = true; } @@ -656,7 +647,7 @@ public class WindowTransformSwipeHandler @Override public void onGestureCancelled() { updateDisplacement(0); - setStateOnUiThread(STATE_GESTURE_COMPLETED); + mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED); mLogAction = Touch.SWIPE_NOOP; handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */); } @@ -671,7 +662,7 @@ public class WindowTransformSwipeHandler float flingThreshold = mContext.getResources() .getDimension(R.dimen.quickstep_fling_threshold_velocity); boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold; - setStateOnUiThread(STATE_GESTURE_COMPLETED); + mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED); mLogAction = isFling ? Touch.FLING : Touch.SWIPE; boolean isVelocityVertical = Math.abs(velocity.y) > Math.abs(velocity.x); @@ -686,11 +677,11 @@ public class WindowTransformSwipeHandler @Override protected InputConsumer createNewInputProxyHandler() { - endRunningWindowAnim(mGestureEndTarget == HOME /* cancel */); + endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */); endLauncherTransitionController(); if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) { // Hide the task view, if not already hidden - setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha); + setTargetAlphaProvider(LauncherSwipeHandler::getHiddenTargetAlpha); } BaseDraggingActivity activity = mActivityInterface.getCreatedActivity(); @@ -708,6 +699,24 @@ public class WindowTransformSwipeHandler } } + private void onEndTargetSet() { + switch (mGestureState.getEndTarget()) { + case HOME: + mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT); + break; + case RECENTS: + mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT + | STATE_SCREENSHOT_VIEW_SHOWN); + break; + case NEW_TASK: + mStateCallback.setState(STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT); + break; + case LAST_TASK: + mStateCallback.setState(STATE_RESUME_LAST_TASK); + break; + } + } + private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling, boolean isCancel) { final GestureEndTarget endTarget; @@ -729,7 +738,7 @@ public class WindowTransformSwipeHandler if (!isFling) { if (isCancel) { endTarget = LAST_TASK; - } else if (mMode == Mode.NO_BUTTON) { + } else if (mDeviceState.isFullyGesturalNavMode()) { if (mIsShelfPeeking) { endTarget = RECENTS; } else if (goingToNewTask) { @@ -750,9 +759,9 @@ public class WindowTransformSwipeHandler boolean willGoToNewTaskOnSwipeUp = goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity); - if (mMode == Mode.NO_BUTTON && isSwipeUp && !willGoToNewTaskOnSwipeUp) { + if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !willGoToNewTaskOnSwipeUp) { endTarget = HOME; - } else if (mMode == Mode.NO_BUTTON && isSwipeUp && !mIsShelfPeeking) { + } else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !mIsShelfPeeking) { // If swiping at a diagonal, base end target on the faster velocity. endTarget = NEW_TASK; } else if (isSwipeUp) { @@ -777,7 +786,7 @@ public class WindowTransformSwipeHandler float currentShift = mCurrentShift.value; final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity, isFling, isCancel); - float endShift = endTarget.endShift; + float endShift = endTarget.isLauncher ? 1 : 0; final float startShift; Interpolator interpolator = DEACCEL; if (!isFling) { @@ -792,7 +801,7 @@ public class WindowTransformSwipeHandler float minFlingVelocity = mContext.getResources() .getDimension(R.dimen.quickstep_fling_min_velocity); if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) { - if (endTarget == RECENTS && mMode != Mode.NO_BUTTON) { + if (endTarget == RECENTS && !mDeviceState.isFullyGesturalNavMode()) { Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams( startShift, endShift, endShift, endVelocity / 1000, mTransitionDragLength, mContext); @@ -825,7 +834,7 @@ public class WindowTransformSwipeHandler setShelfState(ShelfAnimState.CANCEL, LINEAR, 0); duration = Math.max(MIN_OVERSHOOT_DURATION, duration); } else if (endTarget == RECENTS) { - mLiveTileOverlay.startIconAnimation(); + LiveTileOverlay.getInstance().startIconAnimation(); if (mRecentsView != null) { int nearestPage = mRecentsView.getPageNearestToCenterOfScreen(); if (mRecentsView.getNextPage() != nearestPage) { @@ -838,7 +847,7 @@ public class WindowTransformSwipeHandler } duration = Math.max(duration, mRecentsView.getScroller().getDuration()); } - if (mMode == Mode.NO_BUTTON) { + if (mDeviceState.isFullyGesturalNavMode()) { setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration); } } else if (endTarget == NEW_TASK || endTarget == LAST_TASK) { @@ -880,14 +889,15 @@ public class WindowTransformSwipeHandler @UiThread private void animateToProgressInternal(float start, float end, long duration, Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) { - mGestureEndTarget = target; + // Set the state, but don't notify until the animation completes + mGestureState.setEndTarget(target, false /* isAtomic */); maybeUpdateRecentsAttachedState(); - if (mGestureEndTarget == HOME) { + if (mGestureState.getEndTarget() == HOME) { HomeAnimationFactory homeAnimFactory; if (mActivity != null) { - homeAnimFactory = mActivityInterface.prepareHomeUI(mActivity); + homeAnimFactory = mActivityInterface.prepareHomeUI(); } else { homeAnimFactory = new HomeAnimationFactory() { @NonNull @@ -904,14 +914,15 @@ public class WindowTransformSwipeHandler return AnimatorPlaybackController.wrap(new AnimatorSet(), duration); } }; - mStateCallback.addChangeHandler(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED, + mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED, isPresent -> mRecentsView.startHome()); } RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory); windowAnim.addAnimatorListener(new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animator) { - setStateOnUiThread(target.endState); + // Finalize the state and notify of the change + mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED); } }); windowAnim.start(velocityPxPerMs); @@ -936,10 +947,9 @@ public class WindowTransformSwipeHandler // We are about to launch the current running task, so use LAST_TASK state // instead of NEW_TASK. This could happen, for example, if our scroll is // aborted after we determined the target to be NEW_TASK. - setStateOnUiThread(LAST_TASK.endState); - } else { - setStateOnUiThread(target.endState); + mGestureState.setEndTarget(LAST_TASK); } + mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED); } }); windowAnim.start(); @@ -947,7 +957,7 @@ public class WindowTransformSwipeHandler } // Always play the entire launcher animation when going home, since it is separate from // the animation that has been controlled thus far. - if (mGestureEndTarget == HOME) { + if (mGestureState.getEndTarget() == HOME) { start = 0; } @@ -999,21 +1009,22 @@ public class WindowTransformSwipeHandler } // Make sure recents is in its final state maybeUpdateRecentsAttachedState(false); - mActivityInterface.onSwipeUpToHomeComplete(mActivity); + mActivityInterface.onSwipeUpToHomeComplete(); } }); return anim; } @Override - public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) { - if (mGestureEndTarget != null) { - sharedState.canGestureBeContinued = mGestureEndTarget.canBeContinued; - sharedState.goingToLauncher = mGestureEndTarget.isLauncher; + public void onConsumerAboutToBeSwitched() { + if (mActivity != null) { + // In the off chance that the gesture ends before Launcher is started, we should clear + // the callback here so that it doesn't update with the wrong state + mActivity.clearRunOnceOnStartCallback(); + resetLauncherListenersAndOverlays(); } - - if (sharedState.canGestureBeContinued) { - cancelCurrentAnimation(sharedState); + if (mGestureState.getEndTarget() != null && !mGestureState.isRunningAnimationToLauncher()) { + cancelCurrentAnimation(); } else { reset(); } @@ -1033,6 +1044,15 @@ public class WindowTransformSwipeHandler @UiThread private void startNewTask() { + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + mRecentsAnimationController.finish(true /* toRecents */, this::startNewTaskInternal); + } else { + startNewTaskInternal(); + } + } + + @UiThread + private void startNewTaskInternal() { startNewTask(STATE_HANDLER_INVALIDATED, success -> { if (!success) { // We couldn't launch the task, so take user to overview so they can @@ -1045,14 +1065,14 @@ public class WindowTransformSwipeHandler } private void reset() { - setStateOnUiThread(STATE_HANDLER_INVALIDATED); + mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED); } /** * Cancels any running animation so that the active target can be overriden by a new swipe * handle (in case of quick switch). */ - private void cancelCurrentAnimation(SwipeSharedState sharedState) { + private void cancelCurrentAnimation() { mCanceled = true; mCurrentShift.cancelAnimation(); if (mLauncherTransitionController != null && mLauncherTransitionController @@ -1070,7 +1090,7 @@ public class WindowTransformSwipeHandler ? newRunningTaskView.getTask().key.id : -1; mRecentsView.setCurrentTask(newRunningTaskId); - sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId); + mGestureState.setFinishingRecentsAnimationTaskId(newRunningTaskId); } } @@ -1089,9 +1109,7 @@ public class WindowTransformSwipeHandler endLauncherTransitionController(); mRecentsView.onGestureAnimationEnd(); - - mActivity.getRootView().setOnApplyWindowInsetsListener(null); - removeLiveTileOverlay(); + resetLauncherListenersAndOverlays(); } private void endLauncherTransitionController() { @@ -1102,58 +1120,71 @@ public class WindowTransformSwipeHandler } } + private void resetLauncherListenersAndOverlays() { + // Reset the callback for deferred activity launches + if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) { + mActivityInterface.setOnDeferredActivityLaunchCallback(null); + } + mActivity.getRootView().setOnApplyWindowInsetsListener(null); + removeLiveTileOverlay(); + } + private void notifyTransitionCancelled() { mAnimationFactory.onTransitionCancelled(); } private void resetStateForAnimationCancel() { boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted; - mActivityInterface.onTransitionCancelled(mActivity, wasVisible); + mActivityInterface.onTransitionCancelled(wasVisible); // Leave the pending invisible flag, as it may be used by wallpaper open animation. mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER); } private void switchToScreenshot() { + final int runningTaskId = mGestureState.getRunningTaskId(); if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { if (mRecentsAnimationController != null) { + SharedApiCompat.setWillFinishToHome(mRecentsAnimationController.getController(), + true /* willFinishToHome */); // Update the screenshot of the task if (mTaskSnapshot == null) { - mTaskSnapshot = mRecentsAnimationController.screenshotTask(mRunningTaskId); + mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId); } - mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot, false /* refreshNow */); + mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot, false /* refreshNow */); } - setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); + mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); } else if (!hasTargets()) { // If there are no targets, then we don't need to capture anything - setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); + mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); } else { boolean finishTransitionPosted = false; if (mRecentsAnimationController != null) { // Update the screenshot of the task if (mTaskSnapshot == null) { - mTaskSnapshot = mRecentsAnimationController.screenshotTask(mRunningTaskId); + mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId); } final TaskView taskView; - if (mGestureEndTarget == HOME) { + if (mGestureState.getEndTarget() == HOME) { // Capture the screenshot before finishing the transition to home to ensure it's // taken in the correct orientation, but no need to update the thumbnail. taskView = null; } else { - taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot); + taskView = mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot); } if (taskView != null && !mCanceled) { // Defer finishing the animation until the next launcher frame with the // new thumbnail finishTransitionPosted = ViewUtils.postDraw(taskView, - () -> setStateOnUiThread(STATE_SCREENSHOT_CAPTURED), this::isCanceled); + () -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED), + this::isCanceled); } } if (!finishTransitionPosted) { // If we haven't posted a draw callback, set the state immediately. Object traceToken = TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT, TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS); - setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); + mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); TraceHelper.INSTANCE.endSection(traceToken); } } @@ -1161,23 +1192,24 @@ public class WindowTransformSwipeHandler private void finishCurrentTransitionToRecents() { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); - } else if (!hasTargets()) { - // If there are no targets, then there is nothing to finish - setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); + mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); + } else if (!hasTargets() || mRecentsAnimationController == null) { + // If there are no targets or the animation not started, then there is nothing to finish + mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); } else { - synchronized (mRecentsAnimationController) { - mRecentsAnimationController.finish(true /* toRecents */, - () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED)); - } + mRecentsAnimationController.finish(true /* toRecents */, + () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED)); } ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true); } private void finishCurrentTransitionToHome() { - synchronized (mRecentsAnimationController) { + if (!hasTargets() || mRecentsAnimationController == null) { + // If there are no targets or the animation not started, then there is nothing to finish + mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); + } else { mRecentsAnimationController.finish(true /* toRecents */, - () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED), + () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED), true /* sendUserLeaveHint */); } ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true); @@ -1186,15 +1218,14 @@ public class WindowTransformSwipeHandler private void setupLauncherUiAfterSwipeUpToRecentsAnimation() { endLauncherTransitionController(); - mActivityInterface.onSwipeUpToRecentsComplete(mActivity); + mActivityInterface.onSwipeUpToRecentsComplete(); if (mRecentsAnimationController != null) { mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */, true /* screenshot */); } mRecentsView.onSwipeUpAnimationSuccess(); - RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG); - + SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG); doLogGesture(RECENTS); reset(); } @@ -1204,20 +1235,15 @@ public class WindowTransformSwipeHandler updateFinalShift(); } - private synchronized void addLiveTileOverlay() { - if (!mLiveTileOverlayAttached) { - mActivity.getRootView().getOverlay().add(mLiveTileOverlay); - mRecentsView.setLiveTileOverlay(mLiveTileOverlay); - mLiveTileOverlayAttached = true; + private void addLiveTileOverlay() { + if (LiveTileOverlay.getInstance().attach(mActivity.getRootView().getOverlay())) { + mRecentsView.setLiveTileOverlayAttached(true); } } - private synchronized void removeLiveTileOverlay() { - if (mLiveTileOverlayAttached) { - mActivity.getRootView().getOverlay().remove(mLiveTileOverlay); - mRecentsView.setLiveTileOverlay(null); - mLiveTileOverlayAttached = false; - } + private void removeLiveTileOverlay() { + LiveTileOverlay.getInstance().detach(mActivity.getRootView().getOverlay()); + mRecentsView.setLiveTileOverlayAttached(false); } public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, float expectedAlpha) { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java index 150c44d264..c4733bd35a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java @@ -29,6 +29,7 @@ import android.view.ViewConfiguration; import androidx.annotation.BinderThread; import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.quickstep.util.ActivityInitListener; @@ -203,7 +204,8 @@ public class OverviewCommandHelper { return false; } - private boolean onActivityReady(T activity, Boolean wasVisible) { + private boolean onActivityReady(Boolean wasVisible) { + final T activity = mActivityInterface.getCreatedActivity(); if (!mUserEventLogged) { activity.getUserEventDispatcher().logActionCommand( LauncherLogProto.Action.Command.RECENTS_BUTTON, @@ -211,6 +213,10 @@ public class OverviewCommandHelper { LauncherLogProto.ContainerType.TASKSWITCHER); mUserEventLogged = true; } + + // Switch prediction client to overview + PredictionUiStateManager.INSTANCE.get(activity).switchClient( + PredictionUiStateManager.Client.OVERVIEW); return mAnimationProvider.onActivityReady(activity, wasVisible); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java index ce533a699a..92c55da6da 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java @@ -10,7 +10,6 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.testing.TestInformationHandler; import com.android.launcher3.testing.TestProtocol; -import com.android.launcher3.uioverrides.states.OverviewState; import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.views.RecentsView; @@ -34,7 +33,7 @@ public class QuickstepTestInformationHandler extends TestInformationHandler { switch (method) { case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: { final float swipeHeight = - OverviewState.getDefaultSwipeHeight(mContext, mDeviceProfile); + LayoutUtils.getDefaultSwipeHeight(mContext, mDeviceProfile); response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight); return response; } @@ -112,11 +111,6 @@ public class QuickstepTestInformationHandler extends TestInformationHandler { @Override protected boolean isLauncherInitialized() { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, - "isLauncherInitialized.TouchInteractionService.isInitialized=" + - TouchInteractionService.isInitialized()); - } return super.isLauncherInitialized() && TouchInteractionService.isInitialized(); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java index 7b8ec4d886..e0e20ee27e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java @@ -89,9 +89,13 @@ public final class RecentsActivity extends BaseRecentsActivity { int taskID = intent.getIntExtra(EXTRA_TASK_ID, 0); IBinder thumbnail = intent.getExtras().getBinder(EXTRA_THUMBNAIL); if (taskID != 0 && thumbnail instanceof ObjectWrapper) { - ThumbnailData thumbnailData = ((ObjectWrapper) thumbnail).get(); + ObjectWrapper obj = (ObjectWrapper) thumbnail; + ThumbnailData thumbnailData = obj.get(); mFallbackRecentsView.showCurrentTask(taskID); mFallbackRecentsView.updateThumbnail(taskID, thumbnailData); + // Clear the ref since any reference to the extras on the system side will still + // hold a reference to the wrapper + obj.clear(); } } intent.removeExtra(EXTRA_TASK_ID); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java deleted file mode 100644 index cd8e1a406b..0000000000 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep; - -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; - -import android.util.Log; - -import com.android.launcher3.Utilities; -import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.util.Preconditions; -import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener; - -import com.android.systemui.shared.recents.model.ThumbnailData; - -import java.io.PrintWriter; - -/** - * Utility class used to store state information shared across multiple transitions. - */ -public class SwipeSharedState implements RecentsAnimationListener { - - private OverviewComponentObserver mOverviewComponentObserver; - - private RecentsAnimationCallbacks mRecentsAnimationListener; - private RecentsAnimationController mLastRecentsAnimationController; - private RecentsAnimationTargets mLastAnimationTarget; - - private boolean mLastAnimationCancelled = false; - private boolean mLastAnimationRunning = false; - - public boolean canGestureBeContinued; - public boolean goingToLauncher; - public boolean recentsAnimationFinishInterrupted; - public int nextRunningTaskId = -1; - private int mLogId; - - public void setOverviewComponentObserver(OverviewComponentObserver observer) { - mOverviewComponentObserver = observer; - } - - @Override - public final void onRecentsAnimationStart(RecentsAnimationController controller, - RecentsAnimationTargets targets) { - mLastRecentsAnimationController = controller; - mLastAnimationTarget = targets; - - mLastAnimationCancelled = false; - mLastAnimationRunning = true; - } - - @Override - public final void onRecentsAnimationCanceled(ThumbnailData thumbnailData) { - if (thumbnailData != null) { - mOverviewComponentObserver.getActivityInterface().switchToScreenshot(thumbnailData, - () -> { - mLastRecentsAnimationController.cleanupScreenshot(); - clearAnimationState(); - }); - } else { - clearAnimationState(); - } - } - - @Override - public final void onRecentsAnimationFinished(RecentsAnimationController controller) { - if (mLastRecentsAnimationController == controller) { - mLastAnimationRunning = false; - } - } - - private void clearAnimationTarget() { - if (mLastAnimationTarget != null) { - mLastAnimationTarget.release(); - mLastAnimationTarget = null; - } - } - - private void clearAnimationState() { - clearAnimationTarget(); - - mLastAnimationCancelled = true; - mLastAnimationRunning = false; - } - - private void clearListenerState(boolean finishAnimation) { - if (mRecentsAnimationListener != null) { - mRecentsAnimationListener.removeListener(this); - mRecentsAnimationListener.notifyAnimationCanceled(); - if (mLastAnimationRunning && mLastRecentsAnimationController != null) { - Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), - finishAnimation - ? mLastRecentsAnimationController::finishAnimationToHome - : mLastRecentsAnimationController::finishAnimationToApp); - mLastRecentsAnimationController = null; - mLastAnimationTarget = null; - } - } - mRecentsAnimationListener = null; - clearAnimationTarget(); - mLastAnimationCancelled = false; - mLastAnimationRunning = false; - } - - public RecentsAnimationCallbacks newRecentsAnimationCallbacks() { - Preconditions.assertUIThread(); - - if (mLastAnimationRunning) { - String msg = "New animation started before completing old animation"; - if (FeatureFlags.IS_DOGFOOD_BUILD) { - throw new IllegalArgumentException(msg); - } else { - Log.e("SwipeSharedState", msg, new Exception()); - } - } - - clearListenerState(false /* finishAnimation */); - boolean shouldMinimiseSplitScreen = mOverviewComponentObserver == null ? false - : mOverviewComponentObserver.getActivityInterface().shouldMinimizeSplitScreen(); - mRecentsAnimationListener = new RecentsAnimationCallbacks(shouldMinimiseSplitScreen); - mRecentsAnimationListener.addListener(this); - return mRecentsAnimationListener; - } - - public RecentsAnimationCallbacks getActiveListener() { - return mRecentsAnimationListener; - } - - public void applyActiveRecentsAnimationState(RecentsAnimationListener listener) { - if (mLastRecentsAnimationController != null) { - listener.onRecentsAnimationStart(mLastRecentsAnimationController, - mLastAnimationTarget); - } else if (mLastAnimationCancelled) { - listener.onRecentsAnimationCanceled(null); - } - } - - /** - * Called when a recents animation has finished, but was interrupted before the next task was - * launched. The given {@param runningTaskId} should be used as the running task for the - * continuing input consumer. - */ - public void setRecentsAnimationFinishInterrupted(int runningTaskId) { - recentsAnimationFinishInterrupted = true; - nextRunningTaskId = runningTaskId; - mLastAnimationTarget = mLastAnimationTarget.cloneWithoutTargets(); - } - - public void clearAllState(boolean finishAnimation) { - clearListenerState(finishAnimation); - canGestureBeContinued = false; - recentsAnimationFinishInterrupted = false; - nextRunningTaskId = -1; - goingToLauncher = false; - } - - public void dump(String prefix, PrintWriter pw) { - pw.println(prefix + "goingToLauncher=" + goingToLauncher); - pw.println(prefix + "canGestureBeContinued=" + canGestureBeContinued); - pw.println(prefix + "recentsAnimationFinishInterrupted=" + recentsAnimationFinishInterrupted); - pw.println(prefix + "nextRunningTaskId=" + nextRunningTaskId); - pw.println(prefix + "lastAnimationCancelled=" + mLastAnimationCancelled); - pw.println(prefix + "lastAnimationRunning=" + mLastAnimationRunning); - pw.println(prefix + "logTraceId=" + mLogId); - } - - public void setLogTraceId(int logId) { - this.mLogId = logId; - } -} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java index 17457aace6..b5441dfd14 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java @@ -19,11 +19,11 @@ package com.android.quickstep; import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; import android.graphics.Matrix; -import android.view.View; import com.android.launcher3.BaseActivity; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; +import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.ResourceBasedOverride; import com.android.quickstep.views.TaskThumbnailView; @@ -40,30 +40,30 @@ import java.util.List; public class TaskOverlayFactory implements ResourceBasedOverride { /** Note that these will be shown in order from top to bottom, if available for the task. */ - private static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[]{ - new TaskSystemShortcut.AppInfo(), - new TaskSystemShortcut.SplitScreen(), - new TaskSystemShortcut.Pin(), - new TaskSystemShortcut.Install(), - new TaskSystemShortcut.Freeform() + private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{ + TaskShortcutFactory.APP_INFO, + TaskShortcutFactory.SPLIT_SCREEN, + TaskShortcutFactory.PIN, + TaskShortcutFactory.INSTALL, + TaskShortcutFactory.FREE_FORM, + TaskShortcutFactory.WELLBEING }; - public static final MainThreadInitializedObject INSTANCE = - forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class); - - public List getEnabledShortcuts(TaskView taskView) { - final ArrayList shortcuts = new ArrayList<>(); + public static List getEnabledShortcuts(TaskView taskView) { + final ArrayList shortcuts = new ArrayList<>(); final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext()); - for (TaskSystemShortcut menuOption : MENU_OPTIONS) { - View.OnClickListener onClickListener = - menuOption.getOnClickListener(activity, taskView); - if (onClickListener != null) { - shortcuts.add(menuOption); + for (TaskShortcutFactory menuOption : MENU_OPTIONS) { + SystemShortcut shortcut = menuOption.getShortcut(activity, taskView); + if (shortcut != null) { + shortcuts.add(shortcut); } } return shortcuts; } + public static final MainThreadInitializedObject INSTANCE = + forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class); + public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) { return new TaskOverlay(); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java new file mode 100644 index 0000000000..9ba2e5a684 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP; + +import android.app.Activity; +import android.app.ActivityOptions; +import android.content.ComponentName; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.view.View; + +import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.R; +import com.android.launcher3.WorkspaceItemInfo; +import com.android.launcher3.model.WellbeingModel; +import com.android.launcher3.popup.SystemShortcut; +import com.android.launcher3.popup.SystemShortcut.AppInfo; +import com.android.launcher3.userevent.nano.LauncherLogProto; +import com.android.launcher3.util.Executors; +import com.android.launcher3.util.InstantAppResolver; +import com.android.quickstep.views.RecentsView; +import com.android.quickstep.views.TaskThumbnailView; +import com.android.quickstep.views.TaskView; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat; +import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture; +import com.android.systemui.shared.recents.view.RecentsTransition; +import com.android.systemui.shared.system.ActivityCompat; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.ActivityOptionsCompat; +import com.android.systemui.shared.system.WindowManagerWrapper; + +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +/** + * Represents a system shortcut that can be shown for a recent task. + */ +public interface TaskShortcutFactory { + + SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView view); + + static WorkspaceItemInfo dummyInfo(TaskView view) { + Task task = view.getTask(); + + WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo(); + dummyInfo.intent = new Intent(); + ComponentName component = task.getTopComponent(); + dummyInfo.intent.setComponent(component); + dummyInfo.user = UserHandle.of(task.key.userId); + dummyInfo.title = TaskUtils.getTitle(view.getContext(), task); + return dummyInfo; + } + + TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, dummyInfo(view)); + + abstract class MultiWindowFactory implements TaskShortcutFactory { + + private final int mIconRes; + private final int mTextRes; + + MultiWindowFactory(int iconRes, int textRes) { + mIconRes = iconRes; + mTextRes = textRes; + } + + protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId); + protected abstract ActivityOptions makeLaunchOptions(Activity activity); + protected abstract boolean onActivityStarted(BaseDraggingActivity activity); + + @Override + public SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView taskView) { + final Task task = taskView.getTask(); + if (!task.isDockable) { + return null; + } + if (!isAvailable(activity, task.key.displayId)) { + return null; + } + return new MultiWindowSystemShortcut(mIconRes, mTextRes, activity, taskView, this); + } + } + + class MultiWindowSystemShortcut extends SystemShortcut { + + private Handler mHandler; + + private final RecentsView mRecentsView; + private final TaskThumbnailView mThumbnailView; + private final TaskView mTaskView; + private final MultiWindowFactory mFactory; + + public MultiWindowSystemShortcut(int iconRes, int textRes, + BaseDraggingActivity activity, TaskView taskView, MultiWindowFactory factory) { + super(iconRes, textRes, activity, dummyInfo(taskView)); + + mHandler = new Handler(Looper.getMainLooper()); + mTaskView = taskView; + mRecentsView = activity.getOverviewPanel(); + mThumbnailView = taskView.getThumbnail(); + mFactory = factory; + } + + @Override + public void onClick(View view) { + Task.TaskKey taskKey = mTaskView.getTask().key; + final int taskId = taskKey.id; + + final View.OnLayoutChangeListener onLayoutChangeListener = + new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int l, int t, int r, int b, + int oldL, int oldT, int oldR, int oldB) { + mTaskView.getRootView().removeOnLayoutChangeListener(this); + mRecentsView.clearIgnoreResetTask(taskId); + + // Start animating in the side pages once launcher has been resized + mRecentsView.dismissTask(mTaskView, false, false); + } + }; + + final DeviceProfile.OnDeviceProfileChangeListener onDeviceProfileChangeListener = + new DeviceProfile.OnDeviceProfileChangeListener() { + @Override + public void onDeviceProfileChanged(DeviceProfile dp) { + mTarget.removeOnDeviceProfileChangeListener(this); + if (dp.isMultiWindowMode) { + mTaskView.getRootView().addOnLayoutChangeListener( + onLayoutChangeListener); + } + } + }; + + dismissTaskMenuView(mTarget); + + ActivityOptions options = mFactory.makeLaunchOptions(mTarget); + if (options != null + && ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId, + options)) { + if (!mFactory.onActivityStarted(mTarget)) { + return; + } + // Add a device profile change listener to kick off animating the side tasks + // once we enter multiwindow mode and relayout + mTarget.addOnDeviceProfileChangeListener(onDeviceProfileChangeListener); + + final Runnable animStartedListener = () -> { + // Hide the task view and wait for the window to be resized + // TODO: Consider animating in launcher and do an in-place start activity + // afterwards + mRecentsView.setIgnoreResetTask(taskId); + mTaskView.setAlpha(0f); + }; + + final int[] position = new int[2]; + mThumbnailView.getLocationOnScreen(position); + final int width = (int) (mThumbnailView.getWidth() * mTaskView.getScaleX()); + final int height = (int) (mThumbnailView.getHeight() * mTaskView.getScaleY()); + final Rect taskBounds = new Rect(position[0], position[1], + position[0] + width, position[1] + height); + + // Take the thumbnail of the task without a scrim and apply it back after + float alpha = mThumbnailView.getDimAlpha(); + mThumbnailView.setDimAlpha(0); + Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap( + taskBounds.width(), taskBounds.height(), mThumbnailView, 1f, + Color.BLACK); + mThumbnailView.setDimAlpha(alpha); + + AppTransitionAnimationSpecsFuture future = + new AppTransitionAnimationSpecsFuture(mHandler) { + @Override + public List composeSpecs() { + return Collections.singletonList(new AppTransitionAnimationSpecCompat( + taskId, thumbnail, taskBounds)); + } + }; + WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture( + future, animStartedListener, mHandler, true /* scaleUp */, + taskKey.displayId); + } + } + } + + TaskShortcutFactory SPLIT_SCREEN = new MultiWindowFactory( + R.drawable.ic_split_screen, R.string.recent_task_option_split_screen) { + + @Override + protected boolean isAvailable(BaseDraggingActivity activity, int displayId) { + // Don't show menu-item if already in multi-window and the task is from + // the secondary display. + // TODO(b/118266305): Temporarily disable splitscreen for secondary display while new + // implementation is enabled + return !activity.getDeviceProfile().isMultiWindowMode + && (displayId == -1 || displayId == DEFAULT_DISPLAY); + } + + @Override + protected ActivityOptions makeLaunchOptions(Activity activity) { + final ActivityCompat act = new ActivityCompat(activity); + final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition( + act.getDisplayId()); + if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) { + return null; + } + boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT; + return ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft); + } + + @Override + protected boolean onActivityStarted(BaseDraggingActivity activity) { + SystemUiProxy.INSTANCE.get(activity).onSplitScreenInvoked(); + activity.getUserEventDispatcher().logActionOnControl(TAP, + LauncherLogProto.ControlType.SPLIT_SCREEN_TARGET); + return true; + } + }; + + TaskShortcutFactory FREE_FORM = new MultiWindowFactory( + R.drawable.ic_split_screen, R.string.recent_task_option_freeform) { + + @Override + protected boolean isAvailable(BaseDraggingActivity activity, int displayId) { + return ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity); + } + + @Override + protected ActivityOptions makeLaunchOptions(Activity activity) { + ActivityOptions activityOptions = ActivityOptionsCompat.makeFreeformOptions(); + // Arbitrary bounds only because freeform is in dev mode right now + Rect r = new Rect(50, 50, 200, 200); + activityOptions.setLaunchBounds(r); + return activityOptions; + } + + @Override + protected boolean onActivityStarted(BaseDraggingActivity activity) { + activity.returnToHomescreen(); + return true; + } + }; + + TaskShortcutFactory PIN = (activity, tv) -> { + if (!SystemUiProxy.INSTANCE.get(activity).isActive()) { + return null; + } + if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) { + return null; + } + if (ActivityManagerWrapper.getInstance().isLockToAppActive()) { + // We shouldn't be able to pin while an app is locked. + return null; + } + return new PinSystemShortcut(activity, tv); + }; + + class PinSystemShortcut extends SystemShortcut { + + private static final String TAG = "PinSystemShortcut"; + + private final TaskView mTaskView; + + public PinSystemShortcut(BaseDraggingActivity target, TaskView tv) { + super(R.drawable.ic_pin, R.string.recent_task_option_pin, target, dummyInfo(tv)); + mTaskView = tv; + } + + @Override + public void onClick(View view) { + Consumer resultCallback = success -> { + if (success) { + SystemUiProxy.INSTANCE.get(mTarget).startScreenPinning( + mTaskView.getTask().key.id); + } else { + mTaskView.notifyTaskLaunchFailed(TAG); + } + }; + mTaskView.launchTask(true, resultCallback, Executors.MAIN_EXECUTOR.getHandler()); + dismissTaskMenuView(mTarget); + } + } + + TaskShortcutFactory INSTALL = (activity, view) -> + InstantAppResolver.newInstance(activity).isInstantApp(activity, + view.getTask().getTopComponent().getPackageName()) + ? new SystemShortcut.Install(activity, dummyInfo(view)) : null; + + TaskShortcutFactory WELLBEING = (activity, view) -> + WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, dummyInfo(view)); +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java deleted file mode 100644 index 5a2e3ff821..0000000000 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.quickstep; - -import static android.view.Display.DEFAULT_DISPLAY; -import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP; - -import android.app.Activity; -import android.app.ActivityOptions; -import android.content.ComponentName; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.Rect; -import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; -import android.view.View; - -import com.android.launcher3.BaseDraggingActivity; -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.ItemInfo; -import com.android.launcher3.R; -import com.android.launcher3.WorkspaceItemInfo; -import com.android.launcher3.popup.SystemShortcut; -import com.android.launcher3.userevent.nano.LauncherLogProto; -import com.android.launcher3.util.InstantAppResolver; -import com.android.quickstep.views.RecentsView; -import com.android.quickstep.views.TaskThumbnailView; -import com.android.quickstep.views.TaskView; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat; -import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture; -import com.android.systemui.shared.recents.view.RecentsTransition; -import com.android.systemui.shared.system.ActivityCompat; -import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.ActivityOptionsCompat; -import com.android.systemui.shared.system.WindowManagerWrapper; - -import java.util.Collections; -import java.util.List; -import java.util.function.Consumer; - -/** - * Represents a system shortcut that can be shown for a recent task. - */ -public class TaskSystemShortcut extends SystemShortcut { - - private static final String TAG = "TaskSystemShortcut"; - - protected T mSystemShortcut; - - public TaskSystemShortcut(T systemShortcut) { - super(systemShortcut); - mSystemShortcut = systemShortcut; - } - - protected TaskSystemShortcut(int iconResId, int labelResId) { - super(iconResId, labelResId); - } - - @Override - public View.OnClickListener getOnClickListener( - BaseDraggingActivity activity, ItemInfo itemInfo) { - return null; - } - - public View.OnClickListener getOnClickListener(BaseDraggingActivity activity, TaskView view) { - Task task = view.getTask(); - - WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo(); - dummyInfo.intent = new Intent(); - ComponentName component = task.getTopComponent(); - dummyInfo.intent.setComponent(component); - dummyInfo.user = UserHandle.of(task.key.userId); - dummyInfo.title = TaskUtils.getTitle(activity, task); - - return getOnClickListenerForTask(activity, task, dummyInfo); - } - - protected View.OnClickListener getOnClickListenerForTask( - BaseDraggingActivity activity, Task task, ItemInfo dummyInfo) { - return mSystemShortcut.getOnClickListener(activity, dummyInfo); - } - - public static class AppInfo extends TaskSystemShortcut { - public AppInfo() { - super(new SystemShortcut.AppInfo()); - } - } - - public static abstract class MultiWindow extends TaskSystemShortcut { - - private Handler mHandler; - - public MultiWindow(int iconRes, int textRes) { - super(iconRes, textRes); - mHandler = new Handler(Looper.getMainLooper()); - } - - protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId); - protected abstract ActivityOptions makeLaunchOptions(Activity activity); - protected abstract boolean onActivityStarted(BaseDraggingActivity activity); - - @Override - public View.OnClickListener getOnClickListener( - BaseDraggingActivity activity, TaskView taskView) { - final Task task = taskView.getTask(); - final int taskId = task.key.id; - final int displayId = task.key.displayId; - if (!task.isDockable) { - return null; - } - if (!isAvailable(activity, displayId)) { - return null; - } - final RecentsView recentsView = activity.getOverviewPanel(); - - final TaskThumbnailView thumbnailView = taskView.getThumbnail(); - return (v -> { - final View.OnLayoutChangeListener onLayoutChangeListener = - new View.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int l, int t, int r, int b, - int oldL, int oldT, int oldR, int oldB) { - taskView.getRootView().removeOnLayoutChangeListener(this); - recentsView.clearIgnoreResetTask(taskId); - - // Start animating in the side pages once launcher has been resized - recentsView.dismissTask(taskView, false, false); - } - }; - - final DeviceProfile.OnDeviceProfileChangeListener onDeviceProfileChangeListener = - new DeviceProfile.OnDeviceProfileChangeListener() { - @Override - public void onDeviceProfileChanged(DeviceProfile dp) { - activity.removeOnDeviceProfileChangeListener(this); - if (dp.isMultiWindowMode) { - taskView.getRootView().addOnLayoutChangeListener( - onLayoutChangeListener); - } - } - }; - - dismissTaskMenuView(activity); - - ActivityOptions options = makeLaunchOptions(activity); - if (options != null - && ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId, - options)) { - if (!onActivityStarted(activity)) { - return; - } - // Add a device profile change listener to kick off animating the side tasks - // once we enter multiwindow mode and relayout - activity.addOnDeviceProfileChangeListener(onDeviceProfileChangeListener); - - final Runnable animStartedListener = () -> { - // Hide the task view and wait for the window to be resized - // TODO: Consider animating in launcher and do an in-place start activity - // afterwards - recentsView.setIgnoreResetTask(taskId); - taskView.setAlpha(0f); - }; - - final int[] position = new int[2]; - thumbnailView.getLocationOnScreen(position); - final int width = (int) (thumbnailView.getWidth() * taskView.getScaleX()); - final int height = (int) (thumbnailView.getHeight() * taskView.getScaleY()); - final Rect taskBounds = new Rect(position[0], position[1], - position[0] + width, position[1] + height); - - // Take the thumbnail of the task without a scrim and apply it back after - float alpha = thumbnailView.getDimAlpha(); - thumbnailView.setDimAlpha(0); - Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap( - taskBounds.width(), taskBounds.height(), thumbnailView, 1f, - Color.BLACK); - thumbnailView.setDimAlpha(alpha); - - AppTransitionAnimationSpecsFuture future = - new AppTransitionAnimationSpecsFuture(mHandler) { - @Override - public List composeSpecs() { - return Collections.singletonList(new AppTransitionAnimationSpecCompat( - taskId, thumbnail, taskBounds)); - } - }; - WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture( - future, animStartedListener, mHandler, true /* scaleUp */, displayId); - } - }); - } - } - - public static class SplitScreen extends MultiWindow { - public SplitScreen() { - super(R.drawable.ic_split_screen, R.string.recent_task_option_split_screen); - } - - @Override - protected boolean isAvailable(BaseDraggingActivity activity, int displayId) { - // Don't show menu-item if already in multi-window and the task is from - // the secondary display. - // TODO(b/118266305): Temporarily disable splitscreen for secondary display while new - // implementation is enabled - return !activity.getDeviceProfile().isMultiWindowMode - && (displayId == -1 || displayId == DEFAULT_DISPLAY); - } - - @Override - protected ActivityOptions makeLaunchOptions(Activity activity) { - final ActivityCompat act = new ActivityCompat(activity); - final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition( - act.getDisplayId()); - if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) { - return null; - } - boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT; - return ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft); - } - - @Override - protected boolean onActivityStarted(BaseDraggingActivity activity) { - SystemUiProxy.INSTANCE.get(activity).onSplitScreenInvoked(); - activity.getUserEventDispatcher().logActionOnControl(TAP, - LauncherLogProto.ControlType.SPLIT_SCREEN_TARGET); - return true; - } - } - - public static class Freeform extends MultiWindow { - public Freeform() { - super(R.drawable.ic_split_screen, R.string.recent_task_option_freeform); - } - - @Override - protected boolean isAvailable(BaseDraggingActivity activity, int displayId) { - return ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity); - } - - @Override - protected ActivityOptions makeLaunchOptions(Activity activity) { - ActivityOptions activityOptions = ActivityOptionsCompat.makeFreeformOptions(); - // Arbitrary bounds only because freeform is in dev mode right now - Rect r = new Rect(50, 50, 200, 200); - activityOptions.setLaunchBounds(r); - return activityOptions; - } - - @Override - protected boolean onActivityStarted(BaseDraggingActivity activity) { - activity.returnToHomescreen(); - return true; - } - } - - public static class Pin extends TaskSystemShortcut { - - private static final String TAG = Pin.class.getSimpleName(); - - private Handler mHandler; - - public Pin() { - super(R.drawable.ic_pin, R.string.recent_task_option_pin); - mHandler = new Handler(Looper.getMainLooper()); - } - - @Override - public View.OnClickListener getOnClickListener( - BaseDraggingActivity activity, TaskView taskView) { - if (!SystemUiProxy.INSTANCE.get(activity).isActive()) { - return null; - } - if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) { - return null; - } - if (ActivityManagerWrapper.getInstance().isLockToAppActive()) { - // We shouldn't be able to pin while an app is locked. - return null; - } - return view -> { - Consumer resultCallback = success -> { - if (success) { - SystemUiProxy.INSTANCE.get(activity).startScreenPinning( - taskView.getTask().key.id); - } else { - taskView.notifyTaskLaunchFailed(TAG); - } - }; - taskView.launchTask(true, resultCallback, mHandler); - dismissTaskMenuView(activity); - }; - } - } - - public static class Install extends TaskSystemShortcut { - public Install() { - super(new SystemShortcut.Install()); - } - - @Override - protected View.OnClickListener getOnClickListenerForTask( - BaseDraggingActivity activity, Task task, ItemInfo itemInfo) { - if (InstantAppResolver.newInstance(activity).isInstantApp(activity, - task.getTopComponent().getPackageName())) { - return mSystemShortcut.createOnClickListener(activity, itemInfo); - } - return null; - } - } -} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java index 2522c0fb96..bfe5738ba5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java @@ -172,8 +172,11 @@ public final class TaskViewUtils { AppWindowAnimationHelper.TransformParams liveTileParams = v.getRecentsView().getLiveTileParams(true /* mightNeedToRefill */); if (liveTileParams != null) { - Collections.addAll(surfaceParamsList, - liveTileAnimationHelper.getSurfaceParams(liveTileParams)); + SurfaceParams[] liveTileSurfaceParams = + liveTileAnimationHelper.getSurfaceParams(liveTileParams); + if (liveTileSurfaceParams != null) { + Collections.addAll(surfaceParamsList, liveTileSurfaceParams); + } } } // Apply surface transform using the surface params list. diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index 5591e40b8d..71f8ba4b0f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -33,8 +33,8 @@ import android.annotation.TargetApi; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.Service; -import android.app.TaskInfo; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; @@ -49,12 +49,11 @@ import android.view.InputEvent; import android.view.MotionEvent; import androidx.annotation.BinderThread; +import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; import com.android.launcher3.BaseDraggingActivity; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.config.FeatureFlags; @@ -62,20 +61,21 @@ import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.provider.RestoreDbTask; import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.TraceHelper; -import com.android.quickstep.SysUINavigationMode.Mode; -import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; import com.android.quickstep.inputconsumers.AccessibilityInputConsumer; import com.android.quickstep.inputconsumers.AssistantInputConsumer; import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer; -import com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer; import com.android.quickstep.inputconsumers.OtherActivityInputConsumer; +import com.android.quickstep.inputconsumers.OverscrollInputConsumer; import com.android.quickstep.inputconsumers.OverviewInputConsumer; import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer; -import com.android.quickstep.inputconsumers.QuickCaptureInputConsumer; import com.android.quickstep.inputconsumers.ResetGestureInputConsumer; import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer; import com.android.quickstep.util.ActiveGestureLog; +import com.android.quickstep.util.AssistantUtilities; +import com.android.systemui.plugins.OverscrollPlugin; +import com.android.systemui.plugins.PluginListener; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -83,7 +83,6 @@ import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.RecentsAnimationListener; -import com.android.systemui.shared.system.TaskInfoCompat; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -112,8 +111,7 @@ class ArgList extends LinkedList { * Service connected by system-UI for handling touch interaction. */ @TargetApi(Build.VERSION_CODES.Q) -public class TouchInteractionService extends Service implements - NavigationModeChangeListener { +public class TouchInteractionService extends Service implements PluginListener { private static final String TAG = "TouchInteractionService"; @@ -122,6 +120,8 @@ public class TouchInteractionService extends Service implements private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once"; private static final int MAX_BACK_NOTIFICATION_COUNT = 3; private int mBackGestureNotificationCounter = -1; + @Nullable + private OverscrollPlugin mOverscrollPlugin; private final IBinder mMyBinder = new IOverviewProxy.Stub() { @@ -129,13 +129,11 @@ public class TouchInteractionService extends Service implements public void onInitialize(Bundle bundle) { ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface( bundle.getBinder(KEY_EXTRA_SYSUI_PROXY)); - MAIN_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(TouchInteractionService.this) - .setProxy(proxy)); - MAIN_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor); - MAIN_EXECUTOR.execute(() -> preloadOverview(true /* fromInit */)); - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "TIS initialized"); - } + MAIN_EXECUTOR.execute(() -> { + SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy); + TouchInteractionService.this.initInputMonitor(); + preloadOverview(true /* fromInit */); + }); sIsInitialized = true; } @@ -169,15 +167,19 @@ public class TouchInteractionService extends Service implements @BinderThread @Override public void onAssistantAvailable(boolean available) { - MAIN_EXECUTOR.execute(() -> mDeviceState.setAssistantAvailable(available)); - MAIN_EXECUTOR.execute(TouchInteractionService.this::onAssistantVisibilityChanged); + MAIN_EXECUTOR.execute(() -> { + mDeviceState.setAssistantAvailable(available); + TouchInteractionService.this.onAssistantVisibilityChanged(); + }); } @BinderThread @Override public void onAssistantVisibilityChanged(float visibility) { - MAIN_EXECUTOR.execute(() -> mDeviceState.setAssistantVisibility(visibility)); - MAIN_EXECUTOR.execute(TouchInteractionService.this::onAssistantVisibilityChanged); + MAIN_EXECUTOR.execute(() -> { + mDeviceState.setAssistantVisibility(visibility); + TouchInteractionService.this.onAssistantVisibilityChanged(); + }); } @BinderThread @@ -199,8 +201,10 @@ public class TouchInteractionService extends Service implements @BinderThread public void onSystemUiStateChanged(int stateFlags) { - MAIN_EXECUTOR.execute(() -> mDeviceState.setSystemUiFlags(stateFlags)); - MAIN_EXECUTOR.execute(TouchInteractionService.this::onSystemUiFlagsChanged); + MAIN_EXECUTOR.execute(() -> { + mDeviceState.setSystemUiFlags(stateFlags); + TouchInteractionService.this.onSystemUiFlagsChanged(); + }); } @BinderThread @@ -228,8 +232,6 @@ public class TouchInteractionService extends Service implements private static boolean sConnected = false; private static boolean sIsInitialized = false; - private static final SwipeSharedState sSwipeSharedState = new SwipeSharedState(); - private int mLogId; public static boolean isConnected() { return sConnected; @@ -239,45 +241,38 @@ public class TouchInteractionService extends Service implements return sIsInitialized; } - public static SwipeSharedState getSwipeSharedState() { - return sSwipeSharedState; - } - - private final InputConsumer mResetGestureInputConsumer = - new ResetGestureInputConsumer(sSwipeSharedState); - - private final BaseSwipeUpHandler.Factory mWindowTreansformFactory = - this::createWindowTransformSwipeHandler; - private final BaseSwipeUpHandler.Factory mFallbackNoButtonFactory = - this::createFallbackNoButtonSwipeHandler; + private final BaseSwipeUpHandler.Factory mLauncherSwipeHandlerFactory = + this::createLauncherSwipeHandler; + private final BaseSwipeUpHandler.Factory mFallbackSwipeHandlerFactory = + this::createFallbackSwipeHandler; private ActivityManagerWrapper mAM; - private RecentsModel mRecentsModel; private OverviewCommandHelper mOverviewCommandHelper; private OverviewComponentObserver mOverviewComponentObserver; private InputConsumerController mInputConsumer; private RecentsAnimationDeviceState mDeviceState; + private TaskAnimationManager mTaskAnimationManager; private InputConsumer mUncheckedConsumer = InputConsumer.NO_OP; private InputConsumer mConsumer = InputConsumer.NO_OP; private Choreographer mMainChoreographer; + private InputConsumer mResetGestureInputConsumer; + private GestureState mGestureState = new GestureState(); private InputMonitorCompat mInputMonitorCompat; private InputEventReceiver mInputEventReceiver; - private Mode mMode = Mode.THREE_BUTTONS; @Override public void onCreate() { super.onCreate(); - mDeviceState = new RecentsAnimationDeviceState(this); - mDeviceState.runOnUserUnlocked(this::onUserUnlocked); - // Initialize anything here that is needed in direct boot mode. // Everything else should be initialized in onUserUnlocked() below. mMainChoreographer = Choreographer.getInstance(); mAM = ActivityManagerWrapper.getInstance(); + mDeviceState = new RecentsAnimationDeviceState(this); + mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged); + mDeviceState.runOnUserUnlocked(this::onUserUnlocked); - onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this)); sConnected = true; } @@ -300,7 +295,7 @@ public class TouchInteractionService extends Service implements Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 1"); } disposeEventHandlers(); - if (!mMode.hasGestures || !SystemUiProxy.INSTANCE.get(this).isActive()) { + if (mDeviceState.isButtonNavMode() || !SystemUiProxy.INSTANCE.get(this).isActive()) { return; } if (TestProtocol.sDebugTracing) { @@ -319,25 +314,22 @@ public class TouchInteractionService extends Service implements mDeviceState.updateGestureTouchRegions(); } - @Override - public void onNavigationModeChanged(Mode newMode) { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "onNavigationModeChanged " + newMode); - } - mMode = newMode; + /** + * Called when the navigation mode changes, guaranteed to be after the device state has updated. + */ + private void onNavigationModeChanged(SysUINavigationMode.Mode mode) { initInputMonitor(); resetHomeBounceSeenOnQuickstepEnabledFirstTime(); } @UiThread public void onUserUnlocked() { - mRecentsModel = RecentsModel.INSTANCE.get(this); + mTaskAnimationManager = new TaskAnimationManager(); mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState); mOverviewCommandHelper = new OverviewCommandHelper(this, mDeviceState, mOverviewComponentObserver); + mResetGestureInputConsumer = new ResetGestureInputConsumer(mTaskAnimationManager); mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer(); - - sSwipeSharedState.setOverviewComponentObserver(mOverviewComponentObserver); mInputConsumer.registerInputConsumer(); onSystemUiFlagsChanged(); onAssistantVisibilityChanged(); @@ -347,10 +339,24 @@ public class TouchInteractionService extends Service implements mBackGestureNotificationCounter = Math.max(0, Utilities.getDevicePrefs(this) .getInt(KEY_BACK_NOTIFICATION_COUNT, MAX_BACK_NOTIFICATION_COUNT)); resetHomeBounceSeenOnQuickstepEnabledFirstTime(); + + PluginManagerWrapper.INSTANCE.get(getBaseContext()).addPluginListener(this, + OverscrollPlugin.class, false /* allowMultiple */); + } + + private void onDeferredActivityLaunch() { + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + mOverviewComponentObserver.getActivityInterface().switchRunningTaskViewToScreenshot( + null, () -> { + mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */); + }); + } else { + mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */); + } } private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() { - if (!mDeviceState.isUserUnlocked() || !mMode.hasGestures) { + if (!mDeviceState.isUserUnlocked() || mDeviceState.isButtonNavMode()) { // Skip if not yet unlocked (can't read user shared prefs) or if the current navigation // mode doesn't have gestures return; @@ -385,9 +391,8 @@ public class TouchInteractionService extends Service implements @Override public void onDestroy() { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "TIS destroyed"); - } + PluginManagerWrapper.INSTANCE.get(getBaseContext()).removePluginListener(this); + sIsInitialized = false; if (mDeviceState.isUserUnlocked()) { mInputConsumer.unregisterInputConsumer(); @@ -395,7 +400,6 @@ public class TouchInteractionService extends Service implements } disposeEventHandlers(); mDeviceState.destroy(); - SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this); SystemUiProxy.INSTANCE.get(this).setProxy(null); sConnected = false; @@ -424,19 +428,19 @@ public class TouchInteractionService extends Service implements TraceHelper.FLAG_ALLOW_BINDER_TRACKING); MotionEvent event = (MotionEvent) ev; if (event.getAction() == ACTION_DOWN) { - GestureState newGestureState = new GestureState( - mOverviewComponentObserver.getActivityInterface()); - - mLogId = ActiveGestureLog.INSTANCE.generateAndSetLogId(); - sSwipeSharedState.setLogTraceId(mLogId); + GestureState newGestureState = new GestureState(mOverviewComponentObserver, + ActiveGestureLog.INSTANCE.generateAndSetLogId()); + newGestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0", + () -> mAM.getRunningTask(0))); if (mDeviceState.isInSwipeUpTouchRegion(event)) { - boolean useSharedState = mConsumer.useSharedSwipeState(); mConsumer.onConsumerAboutToBeSwitched(); - mConsumer = newConsumer(newGestureState, useSharedState, event); + mConsumer = newConsumer(mGestureState, newGestureState, event); + ActiveGestureLog.INSTANCE.addLog("setInputConsumer", mConsumer.getType()); mUncheckedConsumer = mConsumer; - } else if (mDeviceState.isUserUnlocked() && mMode == Mode.NO_BUTTON + } else if (mDeviceState.isUserUnlocked() + && mDeviceState.isFullyGesturalNavMode() && mDeviceState.canTriggerAssistantAction(event)) { // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we should // not interrupt it. QuickSwitch assumes that interruption can only happen if the @@ -446,6 +450,9 @@ public class TouchInteractionService extends Service implements } else { mUncheckedConsumer = InputConsumer.NO_OP; } + + // Save the current gesture state + mGestureState = newGestureState; } ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked()); @@ -453,39 +460,41 @@ public class TouchInteractionService extends Service implements TraceHelper.INSTANCE.endFlagsOverride(traceToken); } - private InputConsumer newConsumer(GestureState gestureState, boolean useSharedState, - MotionEvent event) { + private InputConsumer newConsumer(GestureState previousGestureState, + GestureState newGestureState, MotionEvent event) { boolean canStartSystemGesture = mDeviceState.canStartSystemGesture(); if (!mDeviceState.isUserUnlocked()) { if (canStartSystemGesture) { // This handles apps launched in direct boot mode (e.g. dialer) as well as apps // launched while device is locked even after exiting direct boot mode (e.g. camera). - return createDeviceLockedInputConsumer(gestureState, - mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT)); + return createDeviceLockedInputConsumer(newGestureState); } else { return mResetGestureInputConsumer; } } - // When using sharedState, bypass systemState check as this is a followup gesture and the - // first gesture started in a valid system state. - InputConsumer base = canStartSystemGesture || useSharedState - ? newBaseConsumer(gestureState, useSharedState, event) : mResetGestureInputConsumer; - if (mMode == Mode.NO_BUTTON) { + // When there is an existing recents animation running, bypass systemState check as this is + // a followup gesture and the first gesture started in a valid system state. + InputConsumer base = canStartSystemGesture + || previousGestureState.isRecentsAnimationRunning() + ? newBaseConsumer(previousGestureState, newGestureState, event) + : mResetGestureInputConsumer; + if (mDeviceState.isFullyGesturalNavMode()) { if (mDeviceState.canTriggerAssistantAction(event)) { - base = new AssistantInputConsumer(this, gestureState, base, mInputMonitorCompat); + base = new AssistantInputConsumer(this, newGestureState, base, mInputMonitorCompat); } - if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()) { - // Put the Compose gesture as higher priority than the Assistant or base gestures - base = new QuickCaptureInputConsumer(this, gestureState, base, mInputMonitorCompat); + if (mOverscrollPlugin != null) { + // Put the overscroll gesture as higher priority than the Assistant or base gestures + base = new OverscrollInputConsumer(this, newGestureState, base, mInputMonitorCompat, + mOverscrollPlugin); } if (mDeviceState.isScreenPinningActive()) { // Note: we only allow accessibility to wrap this, and it replaces the previous // base input consumer (which should be NO_OP anyway since topTaskLocked == true). - base = new ScreenPinnedInputConsumer(this, gestureState); + base = new ScreenPinnedInputConsumer(this, newGestureState); } if (mDeviceState.isAccessibilityMenuAvailable()) { @@ -500,105 +509,99 @@ public class TouchInteractionService extends Service implements return base; } - private InputConsumer newBaseConsumer(GestureState gestureState, boolean useSharedState, - MotionEvent event) { - RunningTaskInfo runningTaskInfo = TraceHelper.whitelistIpcs("getRunningTask.0", - () -> mAM.getRunningTask(0)); - if (!useSharedState) { - sSwipeSharedState.clearAllState(false /* finishAnimation */); - } + private InputConsumer newBaseConsumer(GestureState previousGestureState, + GestureState gestureState, MotionEvent event) { if (mDeviceState.isKeyguardShowingOccluded()) { // This handles apps showing over the lockscreen (e.g. camera) - return createDeviceLockedInputConsumer(gestureState, runningTaskInfo); + return createDeviceLockedInputConsumer(gestureState); } boolean forceOverviewInputConsumer = false; - if (isExcludedAssistant(runningTaskInfo)) { + if (AssistantUtilities.isExcludedAssistant(gestureState.getRunningTask())) { // In the case where we are in the excluded assistant state, ignore it and treat the // running activity as the task behind the assistant - - runningTaskInfo = TraceHelper.whitelistIpcs("getRunningTask.assistant", - () -> mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT)); - if (!ActivityManagerWrapper.isHomeTask(runningTaskInfo)) { - final ComponentName homeComponent = - mOverviewComponentObserver.getHomeIntent().getComponent(); - forceOverviewInputConsumer = - runningTaskInfo.baseIntent.getComponent(). equals(homeComponent); - } + gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.assistant", + () -> mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT /* ignoreActivityType */))); + ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent(); + ComponentName runningComponent = + gestureState.getRunningTask().baseIntent.getComponent(); + forceOverviewInputConsumer = + runningComponent != null && runningComponent.equals(homeComponent); } - if (runningTaskInfo == null && !sSwipeSharedState.goingToLauncher - && !sSwipeSharedState.recentsAnimationFinishInterrupted) { - return mResetGestureInputConsumer; - } else if (sSwipeSharedState.recentsAnimationFinishInterrupted) { + if (previousGestureState.getFinishingRecentsAnimationTaskId() > 0) { // If the finish animation was interrupted, then continue using the other activity input // consumer but with the next task as the running task RunningTaskInfo info = new ActivityManager.RunningTaskInfo(); - info.id = sSwipeSharedState.nextRunningTaskId; - return createOtherActivityInputConsumer(gestureState, event, info); - } else if (sSwipeSharedState.goingToLauncher + info.id = previousGestureState.getFinishingRecentsAnimationTaskId(); + gestureState.updateRunningTask(info); + return createOtherActivityInputConsumer(previousGestureState, gestureState, event); + } else if (gestureState.getRunningTask() == null) { + return mResetGestureInputConsumer; + } else if (previousGestureState.isRunningAnimationToLauncher() || gestureState.getActivityInterface().isResumed() || forceOverviewInputConsumer) { - return createOverviewInputConsumer(gestureState, event); + return createOverviewInputConsumer( + previousGestureState, gestureState, event, forceOverviewInputConsumer); } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && gestureState.getActivityInterface().isInLiveTileMode()) { - return createOverviewInputConsumer(gestureState, event); - } else if (mDeviceState.isGestureBlockedActivity(runningTaskInfo)) { + return createOverviewInputConsumer( + previousGestureState, gestureState, event, forceOverviewInputConsumer); + } else if (mDeviceState.isGestureBlockedActivity(gestureState.getRunningTask())) { return mResetGestureInputConsumer; } else { - return createOtherActivityInputConsumer(gestureState, event, runningTaskInfo); + return createOtherActivityInputConsumer(previousGestureState, gestureState, event); } } - private boolean isExcludedAssistant(TaskInfo info) { - return info != null - && TaskInfoCompat.getActivityType(info) == ACTIVITY_TYPE_ASSISTANT - && (info.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0; - } - - private InputConsumer createOtherActivityInputConsumer(GestureState gestureState, - MotionEvent event, RunningTaskInfo runningTaskInfo) { + private InputConsumer createOtherActivityInputConsumer(GestureState previousGestureState, + GestureState gestureState, MotionEvent event) { final boolean shouldDefer; final BaseSwipeUpHandler.Factory factory; - if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) { - shouldDefer = !sSwipeSharedState.recentsAnimationFinishInterrupted; - factory = mFallbackNoButtonFactory; + if (mDeviceState.isFullyGesturalNavMode() + && !mOverviewComponentObserver.isHomeAndOverviewSame()) { + shouldDefer = previousGestureState.getFinishingRecentsAnimationTaskId() < 0; + factory = mFallbackSwipeHandlerFactory; } else { shouldDefer = gestureState.getActivityInterface().deferStartingActivity(mDeviceState, event); - factory = mWindowTreansformFactory; + factory = mLauncherSwipeHandlerFactory; } final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event); - return new OtherActivityInputConsumer(this, mDeviceState, gestureState, runningTaskInfo, - shouldDefer, this::onConsumerInactive, sSwipeSharedState, mInputMonitorCompat, - disableHorizontalSwipe, factory, mLogId); + return new OtherActivityInputConsumer(this, mDeviceState, mTaskAnimationManager, + gestureState, shouldDefer, this::onConsumerInactive, + mInputMonitorCompat, disableHorizontalSwipe, factory); } - private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState, - RunningTaskInfo taskInfo) { - if (mMode == Mode.NO_BUTTON && taskInfo != null) { - return new DeviceLockedInputConsumer(this, mDeviceState, gestureState, - sSwipeSharedState, mInputMonitorCompat, taskInfo.taskId, mLogId); + private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState) { + if (mDeviceState.isFullyGesturalNavMode() && gestureState.getRunningTask() != null) { + return new DeviceLockedInputConsumer(this, mDeviceState, mTaskAnimationManager, + gestureState, mInputMonitorCompat); } else { return mResetGestureInputConsumer; } } - public InputConsumer createOverviewInputConsumer(GestureState gestureState, MotionEvent event) { + public InputConsumer createOverviewInputConsumer(GestureState previousGestureState, + GestureState gestureState, MotionEvent event, + boolean forceOverviewInputConsumer) { BaseDraggingActivity activity = gestureState.getActivityInterface().getCreatedActivity(); if (activity == null) { return mResetGestureInputConsumer; } - if (activity.getRootView().hasWindowFocus() || sSwipeSharedState.goingToLauncher) { + if (activity.getRootView().hasWindowFocus() + || previousGestureState.isRunningAnimationToLauncher() + || (FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get() + && forceOverviewInputConsumer)) { return new OverviewInputConsumer(gestureState, activity, mInputMonitorCompat, false /* startingInActivityBounds */); } else { final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event); - return new OverviewWithoutFocusInputConsumer(activity, gestureState, + return new OverviewWithoutFocusInputConsumer(activity, mDeviceState, gestureState, mInputMonitorCompat, disableHorizontalSwipe); } } @@ -617,7 +620,7 @@ public class TouchInteractionService extends Service implements if (!mDeviceState.isUserUnlocked()) { return; } - if (!mMode.hasGestures && !mOverviewComponentObserver.isHomeAndOverviewSame()) { + if (mDeviceState.isButtonNavMode() && !mOverviewComponentObserver.isHomeAndOverviewSame()) { // Prevent the overview from being started before the real home on first boot. return; } @@ -632,8 +635,8 @@ public class TouchInteractionService extends Service implements mOverviewComponentObserver.getActivityInterface(); if (activityInterface.getCreatedActivity() == null) { // Make sure that UI states will be initialized. - activityInterface.createActivityInitListener((activity, wasVisible) -> { - AppLaunchTracker.INSTANCE.get(activity); + activityInterface.createActivityInitListener((wasVisible) -> { + AppLaunchTracker.INSTANCE.get(TouchInteractionService.this); return false; }).register(); } else if (fromInit) { @@ -643,9 +646,8 @@ public class TouchInteractionService extends Service implements return; } - // Pass null animation handler to indicate this start is preload. - startRecentsActivityAsync(mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState(), - null); + mTaskAnimationManager.preloadRecentsAnimation( + mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState()); } @Override @@ -685,14 +687,9 @@ public class TouchInteractionService extends Service implements // Dump everything mDeviceState.dump(pw); pw.println("TouchState:"); - pw.println(" navMode=" + mMode); boolean resumed = mOverviewComponentObserver != null && mOverviewComponentObserver.getActivityInterface().isResumed(); pw.println(" resumed=" + resumed); - pw.println(" useSharedState=" + mConsumer.useSharedSwipeState()); - if (mConsumer.useSharedSwipeState()) { - sSwipeSharedState.dump(" ", pw); - } pw.println(" mConsumer=" + mConsumer.getName()); pw.println("FeatureFlags:"); pw.println(" APPLY_CONFIG_AT_RUNTIME=" + APPLY_CONFIG_AT_RUNTIME.get()); @@ -718,20 +715,16 @@ public class TouchInteractionService extends Service implements } } - private BaseSwipeUpHandler createWindowTransformSwipeHandler(GestureState gestureState, - RunningTaskInfo runningTask, long touchTimeMs, boolean continuingLastGesture, - boolean isLikelyToStartNewTask) { - return new WindowTransformSwipeHandler(this, mDeviceState, gestureState, runningTask, - touchTimeMs, mOverviewComponentObserver, continuingLastGesture, mInputConsumer, - mRecentsModel); + private BaseSwipeUpHandler createLauncherSwipeHandler(GestureState gestureState, + long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) { + return new LauncherSwipeHandler(this, mDeviceState, mTaskAnimationManager, + gestureState, touchTimeMs, continuingLastGesture, mInputConsumer); } - private BaseSwipeUpHandler createFallbackNoButtonSwipeHandler(GestureState gestureState, - RunningTaskInfo runningTask, long touchTimeMs, boolean continuingLastGesture, - boolean isLikelyToStartNewTask) { - return new FallbackNoButtonInputConsumer(this, gestureState, mOverviewComponentObserver, - runningTask, mRecentsModel, mInputConsumer, isLikelyToStartNewTask, - continuingLastGesture); + private BaseSwipeUpHandler createFallbackSwipeHandler(GestureState gestureState, + long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) { + return new FallbackSwipeHandler(this, mDeviceState, gestureState, + mInputConsumer, isLikelyToStartNewTask, continuingLastGesture); } protected boolean shouldNotifyBackGesture() { @@ -754,4 +747,14 @@ public class TouchInteractionService extends Service implements UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance() .startRecentsActivity(intent, null, listener, null, null)); } + + @Override + public void onPluginConnected(OverscrollPlugin overscrollPlugin, Context context) { + mOverscrollPlugin = overscrollPlugin; + } + + @Override + public void onPluginDisconnected(OverscrollPlugin overscrollPlugin) { + mOverscrollPlugin = null; + } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java index 0b5129c079..2f73fc1c57 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java @@ -22,11 +22,6 @@ public abstract class DelegateInputConsumer implements InputConsumer { mState = STATE_INACTIVE; } - @Override - public boolean useSharedSwipeState() { - return mDelegate.useSharedSwipeState(); - } - @Override public boolean allowInterceptByParent() { return mDelegate.allowInterceptByParent() && mState != STATE_ACTIVE; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java index 12b7c2622d..5a345201bf 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java @@ -22,8 +22,7 @@ import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.Utilities.squaredTouchSlop; import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; -import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync; -import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW; +import static com.android.quickstep.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW; import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID; import android.content.ComponentName; @@ -45,9 +44,9 @@ import com.android.quickstep.LockScreenRecentsActivity; import com.android.quickstep.MultiStateCallback; import com.android.quickstep.RecentsAnimationController; import com.android.quickstep.RecentsAnimationDeviceState; -import com.android.quickstep.SwipeSharedState; import com.android.quickstep.RecentsAnimationCallbacks; import com.android.quickstep.RecentsAnimationTargets; +import com.android.quickstep.TaskAnimationManager; import com.android.quickstep.util.AppWindowAnimationHelper; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.InputMonitorCompat; @@ -76,18 +75,16 @@ public class DeviceLockedInputConsumer implements InputConsumer, private final Context mContext; private final RecentsAnimationDeviceState mDeviceState; + private final TaskAnimationManager mTaskAnimationManager; private final GestureState mGestureState; private final float mTouchSlopSquared; - private final SwipeSharedState mSwipeSharedState; private final InputMonitorCompat mInputMonitorCompat; private final PointF mTouchDown = new PointF(); private final AppWindowAnimationHelper mAppWindowAnimationHelper; - private int mLogId; private final AppWindowAnimationHelper.TransformParams mTransformParams; private final Point mDisplaySize; private final MultiStateCallback mStateCallback; - public final int mRunningTaskId; private VelocityTracker mVelocityTracker; private float mProgress; @@ -98,25 +95,23 @@ public class DeviceLockedInputConsumer implements InputConsumer, private RecentsAnimationTargets mRecentsAnimationTargets; public DeviceLockedInputConsumer(Context context, RecentsAnimationDeviceState deviceState, - GestureState gestureState, SwipeSharedState swipeSharedState, - InputMonitorCompat inputMonitorCompat, int runningTaskId, int logId) { + TaskAnimationManager taskAnimationManager, GestureState gestureState, + InputMonitorCompat inputMonitorCompat) { mContext = context; mDeviceState = deviceState; + mTaskAnimationManager = taskAnimationManager; mGestureState = gestureState; mTouchSlopSquared = squaredTouchSlop(context); - mSwipeSharedState = swipeSharedState; mAppWindowAnimationHelper = new AppWindowAnimationHelper(context); - mLogId = logId; mTransformParams = new AppWindowAnimationHelper.TransformParams(); mInputMonitorCompat = inputMonitorCompat; - mRunningTaskId = runningTaskId; // Do not use DeviceProfile as the user data might be locked mDisplaySize = DefaultDisplay.INSTANCE.get(context).getInfo().realSize; // Init states mStateCallback = new MultiStateCallback(STATE_NAMES); - mStateCallback.addCallback(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED, + mStateCallback.runOnceAtState(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED, this::endRemoteAnimation); mVelocityTracker = VelocityTracker.obtain(); @@ -207,16 +202,14 @@ public class DeviceLockedInputConsumer implements InputConsumer, private void startRecentsTransition() { mThresholdCrossed = true; - RecentsAnimationCallbacks callbacks = mSwipeSharedState.newRecentsAnimationCallbacks(); - callbacks.addListener(this); + mInputMonitorCompat.pilferPointers(); + Intent intent = new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_DEFAULT) .setComponent(new ComponentName(mContext, LockScreenRecentsActivity.class)) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK) - .putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId); - - mInputMonitorCompat.pilferPointers(); - startRecentsActivityAsync(intent, callbacks); + .putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId()); + mTaskAnimationManager.startRecentsAnimation(mGestureState, intent, this); } @Override @@ -226,7 +219,8 @@ public class DeviceLockedInputConsumer implements InputConsumer, mRecentsAnimationTargets = targets; Rect displaySize = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y); - RemoteAnimationTargetCompat targetCompat = targets.findTask(mRunningTaskId); + RemoteAnimationTargetCompat targetCompat = targets.findTask( + mGestureState.getRunningTaskId()); if (targetCompat != null) { mAppWindowAnimationHelper.updateSource(displaySize, targetCompat); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java index 02f4c4032d..bf2128dd11 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java @@ -26,12 +26,10 @@ import static android.view.MotionEvent.INVALID_POINTER_ID; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS; -import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync; import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import android.annotation.TargetApi; -import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; @@ -55,13 +53,10 @@ import com.android.quickstep.GestureState; import com.android.quickstep.InputConsumer; import com.android.quickstep.RecentsAnimationCallbacks; import com.android.quickstep.RecentsAnimationDeviceState; -import com.android.quickstep.SwipeSharedState; -import com.android.quickstep.SysUINavigationMode; -import com.android.quickstep.SysUINavigationMode.Mode; +import com.android.quickstep.TaskAnimationManager; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.CachedEventDispatcher; import com.android.quickstep.util.MotionPauseDetector; -import com.android.quickstep.util.NavBarPosition; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputMonitorCompat; @@ -80,21 +75,19 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3; private final RecentsAnimationDeviceState mDeviceState; + private final TaskAnimationManager mTaskAnimationManager; private final GestureState mGestureState; + private RecentsAnimationCallbacks mActiveCallbacks; private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher(); - private final RunningTaskInfo mRunningTask; - private final SwipeSharedState mSwipeSharedState; private final InputMonitorCompat mInputMonitorCompat; - private final SysUINavigationMode.Mode mMode; private final BaseActivityInterface mActivityInterface; private final BaseSwipeUpHandler.Factory mHandlerFactory; - private final NavBarPosition mNavBarPosition; - private final Consumer mOnCompleteCallback; private final MotionPauseDetector mMotionPauseDetector; private final float mMotionPauseMinDisplacement; + private VelocityTracker mVelocityTracker; private BaseSwipeUpHandler mInteractionHandler; @@ -123,20 +116,17 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC ActivityManagerWrapper.getInstance().cancelRecentsAnimation( true /* restoreHomeStackPosition */); }; - private int mLogId; public OtherActivityInputConsumer(Context base, RecentsAnimationDeviceState deviceState, - GestureState gestureState, RunningTaskInfo runningTaskInfo, + TaskAnimationManager taskAnimationManager, GestureState gestureState, boolean isDeferredDownTarget, Consumer onCompleteCallback, - SwipeSharedState swipeSharedState, InputMonitorCompat inputMonitorCompat, - boolean disableHorizontalSwipe, Factory handlerFactory, int logId) { + InputMonitorCompat inputMonitorCompat, boolean disableHorizontalSwipe, + Factory handlerFactory) { super(base); - mLogId = logId; mDeviceState = deviceState; + mTaskAnimationManager = taskAnimationManager; mGestureState = gestureState; mMainThreadHandler = new Handler(Looper.getMainLooper()); - mRunningTask = runningTaskInfo; - mMode = SysUINavigationMode.getMode(base); mHandlerFactory = handlerFactory; mActivityInterface = mGestureState.getActivityInterface(); @@ -147,11 +137,8 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC mVelocityTracker = VelocityTracker.obtain(); mInputMonitorCompat = inputMonitorCompat; - boolean continuingPreviousGesture = swipeSharedState.getActiveListener() != null; + boolean continuingPreviousGesture = mTaskAnimationManager.isRecentsAnimationRunning(); mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget; - mSwipeSharedState = swipeSharedState; - - mNavBarPosition = new NavBarPosition(base); mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop(); float slop = QUICKSTEP_TOUCH_SLOP_RATIO * mTouchSlop; @@ -183,7 +170,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC if (mPassedWindowMoveSlop && mInteractionHandler != null && !mRecentsViewDispatcher.hasConsumer()) { mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher( - mNavBarPosition.getRotationMode())); + mDeviceState.getNavBarPosition().getRotationMode())); } int edgeFlags = ev.getEdgeFlags(); ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR); @@ -293,7 +280,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC mInteractionHandler.updateDisplacement(displacement - mStartDisplacement); } - if (mMode == Mode.NO_BUTTON) { + if (mDeviceState.isFullyGesturalNavMode()) { mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement || isLikelyToStartNewTask); mMotionPauseDetector.addPosition(displacement, ev.getEventTime()); @@ -329,25 +316,22 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC long touchTimeMs, boolean isLikelyToStartNewTask) { ActiveGestureLog.INSTANCE.addLog("startRecentsAnimation"); - RecentsAnimationCallbacks listenerSet = mSwipeSharedState.getActiveListener(); - final BaseSwipeUpHandler handler = mHandlerFactory.newHandler(mGestureState, mRunningTask, - touchTimeMs, listenerSet != null, isLikelyToStartNewTask); + mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs, + mTaskAnimationManager.isRecentsAnimationRunning(), isLikelyToStartNewTask); + mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished); + mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler::onMotionPauseChanged); + mInteractionHandler.initWhenReady(); - mInteractionHandler = handler; - handler.setGestureEndCallback(this::onInteractionGestureFinished); - mMotionPauseDetector.setOnMotionPauseListener(handler::onMotionPauseChanged); - handler.initWhenReady(); - - if (listenerSet != null) { - listenerSet.addListener(handler); - mSwipeSharedState.applyActiveRecentsAnimationState(handler); + if (mTaskAnimationManager.isRecentsAnimationRunning()) { + mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(mGestureState); + mActiveCallbacks.addListener(mInteractionHandler); + mTaskAnimationManager.notifyRecentsAnimationState(mInteractionHandler); notifyGestureStarted(); } else { - RecentsAnimationCallbacks callbacks = mSwipeSharedState.newRecentsAnimationCallbacks(); - callbacks.addListener(handler); - Intent intent = handler.getLaunchIntent(); - intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId); - startRecentsActivityAsync(intent, callbacks); + Intent intent = mInteractionHandler.getLaunchIntent(); + intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId()); + mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(mGestureState, intent, + mInteractionHandler); } } @@ -367,8 +351,10 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC ViewConfiguration.get(this).getScaledMaximumFlingVelocity()); float velocityX = mVelocityTracker.getXVelocity(mActivePointerId); float velocityY = mVelocityTracker.getYVelocity(mActivePointerId); - float velocity = mNavBarPosition.isRightEdge() ? velocityX - : mNavBarPosition.isLeftEdge() ? -velocityX + float velocity = mDeviceState.getNavBarPosition().isRightEdge() + ? velocityX + : mDeviceState.getNavBarPosition().isLeftEdge() + ? -velocityX : velocityY; mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement); @@ -402,7 +388,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC // The consumer is being switched while we are active. Set up the shared state to be // used by the next animation removeListener(); - mInteractionHandler.onConsumerAboutToBeSwitched(mSwipeSharedState); + mInteractionHandler.onConsumerAboutToBeSwitched(); } } @@ -415,27 +401,21 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC } private void removeListener() { - RecentsAnimationCallbacks listenerSet = mSwipeSharedState.getActiveListener(); - if (listenerSet != null) { - listenerSet.removeListener(mInteractionHandler); + if (mActiveCallbacks != null) { + mActiveCallbacks.removeListener(mInteractionHandler); } } private float getDisplacement(MotionEvent ev) { - if (mNavBarPosition.isRightEdge()) { + if (mDeviceState.getNavBarPosition().isRightEdge()) { return ev.getX() - mDownPos.x; - } else if (mNavBarPosition.isLeftEdge()) { + } else if (mDeviceState.getNavBarPosition().isLeftEdge()) { return mDownPos.x - ev.getX(); } else { return ev.getY() - mDownPos.y; } } - @Override - public boolean useSharedSwipeState() { - return mInteractionHandler != null; - } - @Override public boolean allowInterceptByParent() { return !mPassedPilferInputSlop; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java similarity index 71% rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureInputConsumer.java rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java index 97ca730402..e3da98b5ea 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java @@ -24,39 +24,28 @@ import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.Utilities.squaredHypot; -import android.app.ActivityOptions; import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; import android.graphics.PointF; -import android.os.Bundle; import android.view.MotionEvent; import android.view.ViewConfiguration; +import androidx.annotation.Nullable; + import com.android.launcher3.BaseDraggingActivity; -import com.android.launcher3.R; import com.android.quickstep.GestureState; import com.android.quickstep.InputConsumer; import com.android.quickstep.views.RecentsView; +import com.android.systemui.plugins.OverscrollPlugin; import com.android.systemui.shared.system.InputMonitorCompat; /** - * Input consumer for handling events to launch quick capture from launcher + * Input consumer for handling events to pass to an {@code OverscrollPlugin}. + * * @param Draggable activity subclass used by RecentsView */ -public class QuickCaptureInputConsumer - extends DelegateInputConsumer { +public class OverscrollInputConsumer extends DelegateInputConsumer { - private static final String TAG = "QuickCaptureInputConsumer"; - - private static final String QUICK_CAPTURE_PACKAGE = "com.google.auxe.compose"; - private static final String QUICK_CAPTURE_PACKAGE_DEV = "com.google.auxe.compose.debug"; - - private static final String EXTRA_DEVICE_STATE = "deviceState"; - private static final String DEVICE_STATE_LOCKED = "Locked"; - private static final String DEVICE_STATE_LAUNCHER = "Launcher"; - private static final String DEVICE_STATE_APP = "App"; - private static final String DEVICE_STATE_UNKNOWN = "Unknown"; + private static final String TAG = "OverscrollInputConsumer"; private static final int ANGLE_THRESHOLD = 35; // Degrees @@ -69,14 +58,18 @@ public class QuickCaptureInputConsumer private final float mSquaredSlop; - private Context mContext; + private final Context mContext; + private final GestureState mGestureState; + @Nullable private final OverscrollPlugin mPlugin; private RecentsView mRecentsView; - public QuickCaptureInputConsumer(Context context, GestureState gestureState, - InputConsumer delegate, InputMonitorCompat inputMonitor) { + public OverscrollInputConsumer(Context context, GestureState gestureState, + InputConsumer delegate, InputMonitorCompat inputMonitor, OverscrollPlugin plugin) { super(delegate, inputMonitor); mContext = context; + mGestureState = gestureState; + mPlugin = plugin; float slop = ViewConfiguration.get(context).getScaledTouchSlop(); mSquaredSlop = slop * slop; @@ -87,11 +80,11 @@ public class QuickCaptureInputConsumer @Override public int getType() { - return TYPE_QUICK_CAPTURE | mDelegate.getType(); + return TYPE_OVERSCROLL | mDelegate.getType(); } - private boolean onActivityInit(final BaseDraggingActivity activity, Boolean alreadyOnHome) { - mRecentsView = activity.getOverviewPanel(); + private boolean onActivityInit(Boolean alreadyOnHome) { + mRecentsView = mGestureState.getActivityInterface().getCreatedActivity().getOverviewPanel(); return true; } @@ -147,7 +140,7 @@ public class QuickCaptureInputConsumer mPassedSlop = true; mStartDragPos.set(mLastPos.x, mLastPos.y); - if (isValidQuickCaptureGesture()) { + if (isOverscrolled()) { setActive(ev); } else { mState = STATE_DELEGATE_ACTIVE; @@ -159,8 +152,8 @@ public class QuickCaptureInputConsumer } case ACTION_CANCEL: case ACTION_UP: - if (mState != STATE_DELEGATE_ACTIVE && mPassedSlop) { - startQuickCapture(); + if (mState != STATE_DELEGATE_ACTIVE && mPassedSlop && mPlugin != null) { + mPlugin.onOverscroll(getDeviceState()); } mPassedSlop = false; @@ -173,7 +166,7 @@ public class QuickCaptureInputConsumer } } - private boolean isValidQuickCaptureGesture() { + private boolean isOverscrolled() { // Make sure there isn't an app to quick switch to on our right boolean atRightMostApp = (mRecentsView == null || mRecentsView.getRunningTaskIndex() <= 0); @@ -185,37 +178,19 @@ public class QuickCaptureInputConsumer return atRightMostApp && angleInBounds; } - private void startQuickCapture() { - // Inspect our delegate's type to figure out where the user invoked Compose - String deviceState = DEVICE_STATE_UNKNOWN; + private String getDeviceState() { + String deviceState = OverscrollPlugin.DEVICE_STATE_UNKNOWN; int consumerType = mDelegate.getType(); if (((consumerType & InputConsumer.TYPE_OVERVIEW) > 0) || ((consumerType & InputConsumer.TYPE_OVERVIEW_WITHOUT_FOCUS)) > 0) { - deviceState = DEVICE_STATE_LAUNCHER; + deviceState = OverscrollPlugin.DEVICE_STATE_LAUNCHER; } else if ((consumerType & InputConsumer.TYPE_OTHER_ACTIVITY) > 0) { - deviceState = DEVICE_STATE_APP; + deviceState = OverscrollPlugin.DEVICE_STATE_APP; } else if (((consumerType & InputConsumer.TYPE_RESET_GESTURE) > 0) || ((consumerType & InputConsumer.TYPE_DEVICE_LOCKED) > 0)) { - deviceState = DEVICE_STATE_LOCKED; + deviceState = OverscrollPlugin.DEVICE_STATE_LOCKED; } - // Then launch the app - PackageManager pm = mContext.getPackageManager(); - - Intent qcIntent = pm.getLaunchIntentForPackage(QUICK_CAPTURE_PACKAGE); - - if (qcIntent == null) { - // If we couldn't find the regular app, try the dev version - qcIntent = pm.getLaunchIntentForPackage(QUICK_CAPTURE_PACKAGE_DEV); - } - - if (qcIntent != null) { - qcIntent.putExtra(EXTRA_DEVICE_STATE, deviceState); - - Bundle options = ActivityOptions.makeCustomAnimation(mContext, R.anim.slide_in_right, - 0).toBundle(); - - mContext.startActivity(qcIntent, options); - } + return deviceState; } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java index 50069ea05c..875ec29530 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java @@ -21,9 +21,9 @@ import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.Utilities.squaredHypot; -import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import android.content.Context; +import android.content.Intent; import android.graphics.PointF; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -35,36 +35,34 @@ import com.android.launcher3.Utilities; import com.android.launcher3.logging.StatsLogUtils; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; -import com.android.quickstep.BaseActivityInterface; -import com.android.quickstep.InputConsumer; import com.android.quickstep.GestureState; +import com.android.quickstep.InputConsumer; +import com.android.quickstep.RecentsAnimationDeviceState; import com.android.quickstep.util.ActiveGestureLog; -import com.android.quickstep.util.NavBarPosition; -import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputMonitorCompat; public class OverviewWithoutFocusInputConsumer implements InputConsumer { + private final Context mContext; + private final RecentsAnimationDeviceState mDeviceState; + private final GestureState mGestureState; private final InputMonitorCompat mInputMonitor; private final boolean mDisableHorizontalSwipe; private final PointF mDownPos = new PointF(); private final float mSquaredTouchSlop; - private final Context mContext; - private final NavBarPosition mNavBarPosition; - private final BaseActivityInterface mActivityInterface; private boolean mInterceptedTouch; private VelocityTracker mVelocityTracker; - public OverviewWithoutFocusInputConsumer(Context context, GestureState gestureState, + public OverviewWithoutFocusInputConsumer(Context context, + RecentsAnimationDeviceState deviceState, GestureState gestureState, InputMonitorCompat inputMonitor, boolean disableHorizontalSwipe) { + mContext = context; + mDeviceState = deviceState; + mGestureState = gestureState; mInputMonitor = inputMonitor; mDisableHorizontalSwipe = disableHorizontalSwipe; - mContext = context; - mActivityInterface = gestureState.getActivityInterface(); mSquaredTouchSlop = Utilities.squaredTouchSlop(context); - mNavBarPosition = new NavBarPosition(context); - mVelocityTracker = VelocityTracker.obtain(); } @@ -135,8 +133,11 @@ public class OverviewWithoutFocusInputConsumer implements InputConsumer { mVelocityTracker.computeCurrentVelocity(100); float velocityX = mVelocityTracker.getXVelocity(); float velocityY = mVelocityTracker.getYVelocity(); - float velocity = mNavBarPosition.isRightEdge() - ? -velocityX : (mNavBarPosition.isLeftEdge() ? velocityX : -velocityY); + float velocity = mDeviceState.getNavBarPosition().isRightEdge() + ? -velocityX + : mDeviceState.getNavBarPosition().isLeftEdge() + ? velocityX + : -velocityY; final boolean triggerQuickstep; int touch = Touch.FLING; @@ -150,9 +151,9 @@ public class OverviewWithoutFocusInputConsumer implements InputConsumer { } if (triggerQuickstep) { - mActivityInterface.closeOverlay(); - ActivityManagerWrapper.getInstance() - .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); + mContext.startActivity(new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_HOME) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); ActiveGestureLog.INSTANCE.addLog("startQuickstep"); BaseActivity activity = BaseDraggingActivity.fromContext(mContext); int pageIndex = -1; // This number doesn't reflect workspace page index. diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java index e04c0c741c..d34b40bf0c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java @@ -18,17 +18,17 @@ package com.android.quickstep.inputconsumers; import android.view.MotionEvent; import com.android.quickstep.InputConsumer; -import com.android.quickstep.SwipeSharedState; +import com.android.quickstep.TaskAnimationManager; /** * A NO_OP input consumer which also resets any pending gesture */ public class ResetGestureInputConsumer implements InputConsumer { - private final SwipeSharedState mSwipeSharedState; + private final TaskAnimationManager mTaskAnimationManager; - public ResetGestureInputConsumer(SwipeSharedState swipeSharedState) { - mSwipeSharedState = swipeSharedState; + public ResetGestureInputConsumer(TaskAnimationManager taskAnimationManager) { + mTaskAnimationManager = taskAnimationManager; } @Override @@ -39,8 +39,8 @@ public class ResetGestureInputConsumer implements InputConsumer { @Override public void onMotionEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN - && mSwipeSharedState.getActiveListener() != null) { - mSwipeSharedState.clearAllState(false /* finishAnimation */); + && mTaskAnimationManager.isRecentsAnimationRunning()) { + mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */); } } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java new file mode 100644 index 0000000000..b251f9e697 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.logging; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.android.launcher3.ItemInfo; +import com.android.launcher3.appprediction.PredictionUiStateManager; +import com.android.launcher3.userevent.nano.LauncherLogProto; + +/** + * This class handles AOSP MetricsLogger function calls and logging around + * quickstep interactions and app launches. + */ +@SuppressWarnings("unused") +public class UserEventDispatcherAppPredictionExtension extends UserEventDispatcherExtension { + + public static final int ALL_APPS_PREDICTION_TIPS = 2; + + private static final String TAG = "UserEventDispatcher"; + + public UserEventDispatcherAppPredictionExtension(Context context) { + super(context); + } + + @Override + protected void onFillInLogContainerData( + @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target, + @NonNull LauncherLogProto.Target targetParent) { + PredictionUiStateManager.fillInPredictedRank(itemInfo, target); + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java index 9a3bb760f1..fabfc4bb51 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java @@ -33,7 +33,7 @@ public class ActiveGestureLog extends EventLogArray { */ public static final String INTENT_EXTRA_LOG_TRACE_ID = "INTENT_EXTRA_LOG_TRACE_ID"; - public ActiveGestureLog() { + private ActiveGestureLog() { super("touch_interaction_log", 40); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java index 24e7f0ecea..4a39e73e41 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java @@ -169,7 +169,7 @@ public class AppWindowAnimationHelper { return null; } - float progress = params.progress; + float progress = Utilities.boundToRange(params.progress, 0, 1); updateCurrentRect(params); SurfaceParams[] surfaceParams = new SurfaceParams[params.targetSet.unfilteredApps.length]; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AssistantUtilities.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AssistantUtilities.java new file mode 100644 index 0000000000..552db1f4dd --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AssistantUtilities.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.util; + +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT; + +import android.annotation.TargetApi; +import android.app.TaskInfo; +import android.content.Intent; +import android.os.Build; + +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.TaskInfoCompat; + +/** + * Utility class for interacting with the Assistant. + */ +@TargetApi(Build.VERSION_CODES.Q) +public final class AssistantUtilities { + + /** Returns true if an Assistant activity that is excluded from recents is running. */ + public static boolean isExcludedAssistantRunning() { + return isExcludedAssistant(ActivityManagerWrapper.getInstance().getRunningTask()); + } + + /** Returns true if the given task holds an Assistant activity that is excluded from recents. */ + public static boolean isExcludedAssistant(TaskInfo info) { + return info != null + && TaskInfoCompat.getActivityType(info) == ACTIVITY_TYPE_ASSISTANT + && (info.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0; + } + + private AssistantUtilities() {} +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java deleted file mode 100644 index bbb318a027..0000000000 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep.util; - -import static com.android.launcher3.uioverrides.RecentsUiFactory.ROTATION_LANDSCAPE; -import static com.android.launcher3.uioverrides.RecentsUiFactory.ROTATION_SEASCAPE; -import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; - -import android.content.Context; -import android.view.Surface; - -import com.android.launcher3.graphics.RotationMode; -import com.android.launcher3.util.DefaultDisplay; -import com.android.quickstep.SysUINavigationMode; - -/** - * Utility class to check nav bar position - */ -public class NavBarPosition { - - private final SysUINavigationMode.Mode mMode; - private final int mDisplayRotation; - - public NavBarPosition(Context context) { - mMode = SysUINavigationMode.getMode(context); - mDisplayRotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation; - } - - public boolean isRightEdge() { - return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90; - } - - public boolean isLeftEdge() { - return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_270; - } - - public RotationMode getRotationMode() { - return isLeftEdge() ? ROTATION_SEASCAPE - : (isRightEdge() ? ROTATION_LANDSCAPE : RotationMode.NORMAL); - } -} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java new file mode 100644 index 0000000000..41be6834f7 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.util; + +import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_SHELF_ANIM; +import static com.android.launcher3.LauncherState.BACKGROUND_APP; +import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.view.animation.Interpolator; + +import com.android.launcher3.Launcher; +import com.android.launcher3.uioverrides.states.OverviewState; + +/** + * Animates the shelf between states HIDE, PEEK, and OVERVIEW. + */ +public class ShelfPeekAnim { + + public static final Interpolator INTERPOLATOR = OVERSHOOT_1_2; + public static final long DURATION = 240; + + private final Launcher mLauncher; + + private ShelfAnimState mShelfState; + private boolean mIsPeeking; + + public ShelfPeekAnim(Launcher launcher) { + mLauncher = launcher; + } + + /** + * Animates to the given state, canceling the previous animation if it was still running. + */ + public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) { + if (mShelfState == shelfState) { + return; + } + mLauncher.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM); + mShelfState = shelfState; + mIsPeeking = mShelfState == ShelfAnimState.PEEK || mShelfState == ShelfAnimState.HIDE; + if (mShelfState == ShelfAnimState.CANCEL) { + return; + } + float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(mLauncher); + float shelfOverviewProgress = OVERVIEW.getVerticalProgress(mLauncher); + // Peek based on default overview progress so we can see hotseat if we're showing + // that instead of predictions in overview. + float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(mLauncher); + float shelfPeekingProgress = shelfHiddenProgress + - (shelfHiddenProgress - defaultOverviewProgress) * 0.25f; + float toProgress = mShelfState == ShelfAnimState.HIDE + ? shelfHiddenProgress + : mShelfState == ShelfAnimState.PEEK + ? shelfPeekingProgress + : shelfOverviewProgress; + Animator shelfAnim = mLauncher.getStateManager() + .createStateElementAnimation(INDEX_SHELF_ANIM, toProgress); + shelfAnim.setInterpolator(interpolator); + shelfAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + mShelfState = ShelfAnimState.CANCEL; + } + + @Override + public void onAnimationEnd(Animator animator) { + mIsPeeking = mShelfState == ShelfAnimState.PEEK; + } + }); + shelfAnim.setDuration(duration).start(); + } + + /** @return Whether the shelf is currently peeking or animating to or from peeking. */ + public boolean isPeeking() { + return mIsPeeking; + } + + /** The various shelf states we can animate to. */ + public enum ShelfAnimState { + HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false); + + ShelfAnimState(boolean shouldPreformHaptic) { + this.shouldPreformHaptic = shouldPreformHaptic; + } + + public final boolean shouldPreformHaptic; + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java index 0655c733ba..82fbbc6802 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 @@ -102,8 +102,9 @@ public class LauncherRecentsView extends RecentsView implements StateL @Override public void startHome() { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - switchToScreenshot(() -> finishRecentsAnimation(true /* toRecents */, - () -> mActivity.getStateManager().goToState(NORMAL))); + switchToScreenshot(null, + () -> finishRecentsAnimation(true /* toRecents */, + () -> mActivity.getStateManager().goToState(NORMAL))); } else { mActivity.getStateManager().goToState(NORMAL); } @@ -325,8 +326,8 @@ public class LauncherRecentsView extends RecentsView implements StateL @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - PluginManagerWrapper.INSTANCE.get(getContext()) - .addPluginListener(mRecentsExtraCardPluginListener, RecentsExtraCard.class); + PluginManagerWrapper.INSTANCE.get(getContext()).addPluginListener( + mRecentsExtraCardPluginListener, RecentsExtraCard.class); } @Override @@ -377,10 +378,10 @@ public class LauncherRecentsView extends RecentsView implements StateL } @Override - public void resetTaskVisuals() { - super.resetTaskVisuals(); + public void setContentAlpha(float alpha) { + super.setContentAlpha(alpha); if (mRecentsExtraViewContainer != null) { - mRecentsExtraViewContainer.setAlpha(mContentAlpha); + mRecentsExtraViewContainer.setAlpha(alpha); } } } 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 a83879738d..18eda603fa 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 @@ -16,6 +16,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.util.FloatProperty; +import android.view.ViewOverlay; import com.android.launcher3.anim.Interpolators; @@ -36,6 +37,15 @@ public class LiveTileOverlay extends Drawable { } }; + private static LiveTileOverlay sInstance; + + public static LiveTileOverlay getInstance() { + if (sInstance == null) { + sInstance = new LiveTileOverlay(); + } + return sInstance; + } + private final Paint mPaint = new Paint(); private Rect mBoundsRect = new Rect(); @@ -46,8 +56,9 @@ public class LiveTileOverlay extends Drawable { private boolean mDrawEnabled = true; private float mIconAnimationProgress = 0f; + private boolean mIsAttached; - public LiveTileOverlay() { + private LiveTileOverlay() { mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); } @@ -124,6 +135,23 @@ public class LiveTileOverlay extends Drawable { return PixelFormat.TRANSLUCENT; } + public boolean attach(ViewOverlay overlay) { + if (overlay != null && !mIsAttached) { + overlay.add(this); + mIsAttached = true; + return true; + } + + return false; + } + + public void detach(ViewOverlay overlay) { + if (overlay != null) { + overlay.remove(this); + mIsAttached = false; + } + } + 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 434a0c27d4..250a333865 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 @@ -59,6 +59,7 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Handler; +import android.os.UserHandle; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; @@ -104,7 +105,7 @@ import com.android.launcher3.util.ViewPool; import com.android.quickstep.RecentsAnimationController; import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.RecentsModel; -import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener; +import com.android.quickstep.RecentsModel.TaskVisualsChangeListener; import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskUtils; import com.android.quickstep.ViewUtils; @@ -126,7 +127,7 @@ import java.util.function.Consumer; @TargetApi(Build.VERSION_CODES.P) public abstract class RecentsView extends PagedView implements Insettable, TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback, - InvariantDeviceProfile.OnIDPChangeListener, TaskThumbnailChangeListener { + InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener { private static final String TAG = RecentsView.class.getSimpleName(); @@ -306,7 +307,7 @@ public abstract class RecentsView extends PagedView impl private final int mEmptyMessagePadding; private boolean mShowEmptyMessage; private Layout mEmptyTextLayout; - private LiveTileOverlay mLiveTileOverlay; + private boolean mLiveTileOverlayAttached; // Keeps track of the index where the first TaskView should be private int mTaskViewStartIndex = 0; @@ -382,6 +383,21 @@ public abstract class RecentsView extends PagedView impl return null; } + @Override + public void onTaskIconChanged(String pkg, UserHandle user) { + for (int i = 0; i < getTaskViewCount(); i++) { + TaskView tv = getTaskViewAt(i); + Task task = tv.getTask(); + if (task != null && task.key != null && pkg.equals(task.key.getPackageName()) + && task.key.userId == user.getIdentifier()) { + task.icon = null; + if (tv.getIconView().getDrawable() != null) { + tv.onTaskListVisibilityChanged(true /* visible */); + } + } + } + } + /** * Update the thumbnail of the task. * @param refreshNow Refresh immediately if it's true. @@ -468,6 +484,11 @@ public abstract class RecentsView extends PagedView impl public void setOverviewStateEnabled(boolean enabled) { mOverviewStateEnabled = enabled; updateTaskStackListenerState(); + if (!enabled) { + // Reset the running task when leaving overview since it can still have a reference to + // its thumbnail + mTmpRunningTask = null; + } } public void onDigitalWellbeingToastShown() { @@ -859,8 +880,8 @@ public abstract class RecentsView extends PagedView impl */ public void onSwipeUpAnimationSuccess() { if (getRunningTaskView() != null) { - float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlay != null - ? mLiveTileOverlay.cancelIconAnimation() + float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlayAttached + ? LiveTileOverlay.getInstance().cancelIconAnimation() : 0f; animateUpRunningTaskIconScale(startProgress); } @@ -1672,8 +1693,8 @@ public abstract class RecentsView extends PagedView impl if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { final int[] visibleTasks = getVisibleChildrenRange(); - event.setFromIndex(taskViewCount - visibleTasks[1] - 1); - event.setToIndex(taskViewCount - visibleTasks[0] - 1); + event.setFromIndex(taskViewCount - visibleTasks[1]); + event.setToIndex(taskViewCount - visibleTasks[0]); event.setItemCount(taskViewCount); } } @@ -1707,13 +1728,13 @@ public abstract class RecentsView extends PagedView impl mAppWindowAnimationHelper = appWindowAnimationHelper; } - public void setLiveTileOverlay(LiveTileOverlay liveTileOverlay) { - mLiveTileOverlay = liveTileOverlay; + public void setLiveTileOverlayAttached(boolean liveTileOverlayAttached) { + mLiveTileOverlayAttached = liveTileOverlayAttached; } public void updateLiveTileIcon(Drawable icon) { - if (mLiveTileOverlay != null) { - mLiveTileOverlay.setIcon(icon); + if (mLiveTileOverlayAttached) { + LiveTileOverlay.getInstance().setIcon(icon); } } @@ -1725,7 +1746,17 @@ public abstract class RecentsView extends PagedView impl return; } - mRecentsAnimationController.finish(toRecents, onFinishComplete); + mRecentsAnimationController.finish(toRecents, () -> { + if (onFinishComplete != null) { + onFinishComplete.run(); + // After we finish the recents animation, the current task id should be correctly + // reset so that when the task is launched from Overview later, it goes through the + // flow of starting a new task instead of finishing recents animation to app. A + // typical example of this is (1) user swipes up from app to Overview (2) user + // taps on QSB (3) user goes back to Overview and launch the most recent task. + setCurrentTask(-1); + } + }); } public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) { @@ -1828,8 +1859,8 @@ public abstract class RecentsView extends PagedView impl private void updateEnabledOverlays() { int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1; int taskCount = getTaskViewCount(); - for (int i = 0; i < taskCount; i++) { - getTaskViewAt(i).setOverlayEnabled(i == overlayEnabledPage); + for (int i = mTaskViewStartIndex; i < mTaskViewStartIndex + taskCount; i++) { + getTaskViewAtByAbsoluteIndex(i).setOverlayEnabled(i == overlayEnabledPage); } } @@ -1850,20 +1881,20 @@ public abstract class RecentsView extends PagedView impl return Math.max(insets.getSystemGestureInsets().right, insets.getSystemWindowInsetRight()); } - /** If it's in the live tile mode, switch the running task into screenshot mode. */ - public void switchToScreenshot(Runnable onFinishRunnable) { + public void switchToScreenshot(ThumbnailData thumbnailData, Runnable onFinishRunnable) { TaskView taskView = getRunningTaskView(); - if (taskView == null) { - if (onFinishRunnable != null) { - onFinishRunnable.run(); + if (taskView != null) { + taskView.setShowScreenshot(true); + if (thumbnailData != null) { + taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData); + } else { + taskView.getThumbnail().refresh(); } - return; + ViewUtils.postDraw(taskView, onFinishRunnable); + } else { + onFinishRunnable.run(); } - - taskView.setShowScreenshot(true); - taskView.getThumbnail().refresh(); - ViewUtils.postDraw(taskView, onFinishRunnable); } @Override 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 07d079664a..80022b4fe1 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 @@ -16,7 +16,6 @@ package com.android.quickstep.views; -import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA; import android.animation.Animator; @@ -26,7 +25,6 @@ import android.content.Context; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; -import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; @@ -41,16 +39,13 @@ import com.android.launcher3.R; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; -import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.util.Themes; import com.android.launcher3.views.BaseDragLayer; import com.android.quickstep.TaskOverlayFactory; -import com.android.quickstep.TaskSystemShortcut; import com.android.quickstep.TaskUtils; import com.android.quickstep.views.IconView.OnScaleUpdateListener; -import java.util.List; - /** * Contains options for a recent task when long-pressing its icon. */ @@ -197,22 +192,15 @@ public class TaskMenuView extends AbstractFloatingView { params.topMargin = (int) -mThumbnailTopMargin; mTaskIcon.setLayoutParams(params); - final BaseDraggingActivity activity = BaseDraggingActivity.fromContext(getContext()); - final List shortcuts = - TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(taskView); - final int count = shortcuts.size(); - for (int i = 0; i < count; ++i) { - final TaskSystemShortcut menuOption = shortcuts.get(i); - addMenuOption(menuOption, menuOption.getOnClickListener(activity, taskView)); - } + TaskOverlayFactory.getEnabledShortcuts(taskView).forEach(this::addMenuOption); } - private void addMenuOption(TaskSystemShortcut menuOption, OnClickListener onClickListener) { + private void addMenuOption(SystemShortcut menuOption) { ViewGroup menuOptionView = (ViewGroup) mActivity.getLayoutInflater().inflate( R.layout.task_view_menu_option, this, false); menuOption.setIconAndLabelFor( menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text)); - menuOptionView.setOnClickListener(onClickListener); + menuOptionView.setOnClickListener(menuOption); 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 bfb961320d..a1775f4284 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 @@ -53,6 +53,7 @@ import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.logging.UserEventDispatcher; +import com.android.launcher3.popup.SystemShortcut; 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; @@ -61,7 +62,6 @@ import com.android.launcher3.util.ViewPool.Reusable; import com.android.quickstep.RecentsModel; import com.android.quickstep.TaskIconCache; import com.android.quickstep.TaskOverlayFactory; -import com.android.quickstep.TaskSystemShortcut; import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskUtils; import com.android.quickstep.util.TaskCornerRadius; @@ -287,11 +287,19 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { public void launchTask(boolean animate, boolean freezeTaskList, Consumer resultCallback, Handler resultCallbackHandler) { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + RecentsView recentsView = getRecentsView(); if (isRunningTask()) { - getRecentsView().finishRecentsAnimation(false /* toRecents */, + recentsView.finishRecentsAnimation(false /* toRecents */, () -> resultCallbackHandler.post(() -> resultCallback.accept(true))); } else { - launchTaskInternal(animate, freezeTaskList, resultCallback, resultCallbackHandler); + // This is a workaround against the WM issue that app open is not correctly animated + // when recents animation is being cleaned up (b/143774568). When that's possible, + // we should rely on the framework side to cancel the recents animation, and we will + // clean up the screenshot on the launcher side while we launch the next task. + recentsView.switchToScreenshot(null, + () -> recentsView.finishRecentsAnimation(true /* toRecents */, + () -> launchTaskInternal(animate, freezeTaskList, resultCallback, + resultCallbackHandler))); } } else { launchTaskInternal(animate, freezeTaskList, resultCallback, resultCallbackHandler); @@ -713,15 +721,8 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { getContext().getText(R.string.accessibility_close_task))); final Context context = getContext(); - final List shortcuts = - TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(this); - final int count = shortcuts.size(); - for (int i = 0; i < count; ++i) { - final TaskSystemShortcut menuOption = shortcuts.get(i); - OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, this); - if (onClickListener != null) { - info.addAction(menuOption.createAccessibilityAction(context)); - } + for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) { + info.addAction(s.createAccessibilityAction(context)); } if (mDigitalWellBeingToast.hasLimit()) { @@ -734,8 +735,8 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { final RecentsView recentsView = getRecentsView(); final AccessibilityNodeInfo.CollectionItemInfo itemInfo = AccessibilityNodeInfo.CollectionItemInfo.obtain( - 0, 1, recentsView.getChildCount() - recentsView.indexOfChild(this) - 1, 1, - false); + 0, 1, recentsView.getTaskViewCount() - recentsView.indexOfChild(this) - 1, + 1, false); info.setCollectionItemInfo(itemInfo); } @@ -752,16 +753,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { return true; } - final List shortcuts = - TaskOverlayFactory.INSTANCE.get(getContext()).getEnabledShortcuts(this); - final int count = shortcuts.size(); - for (int i = 0; i < count; ++i) { - final TaskSystemShortcut menuOption = shortcuts.get(i); - if (menuOption.hasHandlerForAction(action)) { - OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, this); - if (onClickListener != null) { - onClickListener.onClick(this); - } + for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) { + if (s.hasHandlerForAction(action)) { + s.onClick(this); return true; } } diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml index 98aaceb0bf..327bb14e75 100644 --- a/quickstep/res/values/config.xml +++ b/quickstep/res/values/config.xml @@ -19,8 +19,6 @@ - com.android.quickstep.logging.UserEventDispatcherExtension - com.android.quickstep.logging.StatsLogCompatManager com.android.quickstep.QuickstepTestInformationHandler @@ -33,4 +31,6 @@ 200 20 + + diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java new file mode 100644 index 0000000000..9ea13c640c --- /dev/null +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3; + +import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; +import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON; +import static com.android.launcher3.LauncherState.ALL_APPS; +import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.allapps.DiscoveryBounce.BOUNCE_MAX_COUNT; +import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_COUNT; +import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN; +import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_COUNT; +import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN; + +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.content.Intent; +import android.content.IntentSender; +import android.os.Bundle; +import android.os.CancellationSignal; + +import com.android.launcher3.LauncherState.ScaleAndTranslation; +import com.android.launcher3.LauncherStateManager.StateHandler; +import com.android.launcher3.model.WellbeingModel; +import com.android.launcher3.popup.SystemShortcut; +import com.android.launcher3.proxy.ProxyActivityStarter; +import com.android.launcher3.proxy.StartActivityParams; +import com.android.launcher3.uioverrides.BackButtonAlphaHandler; +import com.android.launcher3.uioverrides.RecentsViewStateController; +import com.android.launcher3.util.UiThreadHelper; +import com.android.quickstep.RecentsModel; +import com.android.quickstep.SysUINavigationMode; +import com.android.quickstep.SysUINavigationMode.Mode; +import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; +import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.util.RemoteFadeOutAnimationListener; +import com.android.quickstep.util.ShelfPeekAnim; + +import java.util.stream.Stream; + +/** + * Extension of Launcher activity to provide quickstep specific functionality + */ +public abstract class BaseQuickstepLauncher extends Launcher + implements NavigationModeChangeListener { + + /** + * Reusable command for applying the back button alpha on the background thread. + */ + public static final UiThreadHelper.AsyncCommand SET_BACK_BUTTON_ALPHA = + (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setBackButtonAlpha( + Float.intBitsToFloat(arg1), arg2 != 0); + + private final ShelfPeekAnim mShelfPeekAnim = new ShelfPeekAnim(this); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + SysUINavigationMode.Mode mode = SysUINavigationMode.INSTANCE.get(this) + .addModeChangeListener(this); + getRotationHelper().setRotationHadDifferentUI(mode != Mode.NO_BUTTON); + + if (!getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)) { + getStateManager().addStateListener(new LauncherStateManager.StateListener() { + @Override + public void onStateTransitionStart(LauncherState toState) { } + + @Override + public void onStateTransitionComplete(LauncherState finalState) { + boolean swipeUpEnabled = SysUINavigationMode.INSTANCE + .get(BaseQuickstepLauncher.this).getMode().hasGestures; + LauncherState prevState = getStateManager().getLastState(); + + if (((swipeUpEnabled && finalState == OVERVIEW) || (!swipeUpEnabled + && finalState == ALL_APPS && prevState == NORMAL) || BOUNCE_MAX_COUNT + <= getSharedPrefs().getInt(HOME_BOUNCE_COUNT, 0))) { + getSharedPrefs().edit().putBoolean(HOME_BOUNCE_SEEN, true).apply(); + getStateManager().removeStateListener(this); + } + } + }); + } + + if (!getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)) { + getStateManager().addStateListener(new LauncherStateManager.StateListener() { + @Override + public void onStateTransitionStart(LauncherState toState) { } + + @Override + public void onStateTransitionComplete(LauncherState finalState) { + LauncherState prevState = getStateManager().getLastState(); + + if ((finalState == ALL_APPS && prevState == OVERVIEW) || BOUNCE_MAX_COUNT + <= getSharedPrefs().getInt(SHELF_BOUNCE_COUNT, 0)) { + getSharedPrefs().edit().putBoolean(SHELF_BOUNCE_SEEN, true).apply(); + getStateManager().removeStateListener(this); + } + } + }); + } + } + + @Override + public void onDestroy() { + SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this); + super.onDestroy(); + } + + @Override + public void onNavigationModeChanged(Mode newMode) { + getDragLayer().recreateControllers(); + getRotationHelper().setRotationHadDifferentUI(newMode != Mode.NO_BUTTON); + } + + @Override + public void onEnterAnimationComplete() { + super.onEnterAnimationComplete(); + // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled + // as a part of quickstep, so that high-res thumbnails can load the next time we enter + // overview + RecentsModel.INSTANCE.get(this).getThumbnailCache() + .getHighResLoadingState().setVisible(true); + } + + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + RecentsModel.INSTANCE.get(this).onTrimMemory(level); + } + + @Override + public void startIntentSenderForResult(IntentSender intent, int requestCode, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) { + if (requestCode != -1) { + mPendingActivityRequestCode = requestCode; + StartActivityParams params = new StartActivityParams(this, requestCode); + params.intentSender = intent; + params.fillInIntent = fillInIntent; + params.flagsMask = flagsMask; + params.flagsValues = flagsValues; + params.extraFlags = extraFlags; + params.options = options; + startActivity(ProxyActivityStarter.getLaunchIntent(this, params)); + } else { + super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, + flagsValues, extraFlags, options); + } + } + + @Override + public void startActivityForResult(Intent intent, int requestCode, Bundle options) { + if (requestCode != -1) { + mPendingActivityRequestCode = -1; + StartActivityParams params = new StartActivityParams(this, requestCode); + params.intent = intent; + params.options = options; + startActivity(ProxyActivityStarter.getLaunchIntent(this, params)); + } else { + super.startActivityForResult(intent, requestCode, options); + } + } + + @Override + protected void onDeferredResumed() { + if (mPendingActivityRequestCode != -1 && isInState(NORMAL)) { + // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher. + onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null); + // ProxyActivityStarter is started with clear task to reset the task after which it + // removes the task itself. + startActivity(ProxyActivityStarter.getLaunchIntent(this, null)); + } + } + + @Override + protected StateHandler[] createStateHandlers() { + return new StateHandler[] { + getAllAppsController(), + getWorkspace(), + new RecentsViewStateController(this), + new BackButtonAlphaHandler(this)}; + } + + @Override + protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() { + if (SysUINavigationMode.getMode(this) == Mode.NO_BUTTON) { + float offscreenTranslationX = getDeviceProfile().widthPx + - getOverviewPanel().getPaddingStart(); + return new ScaleAndTranslation(1f, offscreenTranslationX, 0f); + } + return super.getOverviewScaleAndTranslationForNormalState(); + } + + @Override + public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { + QuickstepAppTransitionManagerImpl appTransitionManager = + (QuickstepAppTransitionManagerImpl) getAppTransitionManager(); + appTransitionManager.setRemoteAnimationProvider((appTargets, wallpaperTargets) -> { + + // On the first call clear the reference. + signal.cancel(); + + ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0); + fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets, + wallpaperTargets)); + AnimatorSet anim = new AnimatorSet(); + anim.play(fadeAnimation); + return anim; + }, signal); + } + + @Override + public void onDragLayerHierarchyChanged() { + onLauncherStateOrFocusChanged(); + } + + @Override + protected void onActivityFlagsChanged(int changeBits) { + if ((changeBits + & (ACTIVITY_STATE_WINDOW_FOCUSED | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) { + onLauncherStateOrFocusChanged(); + } + + super.onActivityFlagsChanged(changeBits); + } + + /** + * Sets the back button visibility based on the current state/window focus. + */ + private void onLauncherStateOrFocusChanged() { + Mode mode = SysUINavigationMode.getMode(this); + boolean shouldBackButtonBeHidden = mode.hasGestures + && getStateManager().getState().hideBackButton + && hasWindowFocus() + && (getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0; + if (shouldBackButtonBeHidden) { + // Show the back button if there is a floating view visible. + shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenViewWithType(this, + TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null; + } + UiThreadHelper.setBackButtonAlphaAsync(this, SET_BACK_BUTTON_ALPHA, + shouldBackButtonBeHidden ? 0f : 1f, true /* animate */); + if (getDragLayer() != null) { + getRootView().setDisallowBackGesture(shouldBackButtonBeHidden); + } + } + + @Override + public void finishBindingItems(int pageBoundFirst) { + super.finishBindingItems(pageBoundFirst); + // Instantiate and initialize WellbeingModel now that its loading won't interfere with + // populating workspace. + // TODO: Find a better place for this + WellbeingModel.get(this); + } + + @Override + public Stream getSupportedShortcuts() { + return Stream.concat(super.getSupportedShortcuts(), + Stream.of(WellbeingModel.SHORTCUT_FACTORY)); + } + + public ShelfPeekAnim getShelfPeekAnim() { + return mShelfPeekAnim; + } +} diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java index 663b1252d3..96340b22e6 100644 --- a/quickstep/src/com/android/launcher3/LauncherInitListener.java +++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java @@ -22,6 +22,7 @@ import android.os.Build; import android.os.CancellationSignal; import android.os.Handler; +import com.android.launcher3.util.ActivityTracker; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.RemoteAnimationProvider; @@ -32,6 +33,11 @@ public class LauncherInitListener extends ActivityInitListener { private RemoteAnimationProvider mRemoteAnimationProvider; + /** + * @param onInitListener a callback made when the activity is initialized. The callback should + * return true to continue receiving callbacks (ie. for if the activity is + * recreated). + */ public LauncherInitListener(BiPredicate onInitListener) { super(onInitListener, Launcher.ACTIVITY_TRACKER); } diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java new file mode 100644 index 0000000000..5aa43888b4 --- /dev/null +++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.model; + +import static android.content.ContentResolver.SCHEME_CONTENT; + +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.createAndStartNewLooper; + +import android.annotation.TargetApi; +import android.app.RemoteAction; +import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.LauncherApps; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.DeadObjectException; +import android.os.Handler; +import android.os.Message; +import android.os.Process; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; + +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; + +import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.R; +import com.android.launcher3.popup.RemoteActionShortcut; +import com.android.launcher3.popup.SystemShortcut; +import com.android.launcher3.util.PackageManagerHelper; +import com.android.launcher3.util.Preconditions; +import com.android.launcher3.util.SimpleBroadcastReceiver; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Data model for digital wellbeing status of apps. + */ +@TargetApi(Build.VERSION_CODES.Q) +public final class WellbeingModel { + private static final String TAG = "WellbeingModel"; + private static final int[] RETRY_TIMES_MS = {5000, 15000, 30000}; + private static final boolean DEBUG = false; + + private static final int MSG_PACKAGE_ADDED = 1; + private static final int MSG_PACKAGE_REMOVED = 2; + private static final int MSG_FULL_REFRESH = 3; + + // Welbeing contract + private static final String METHOD_GET_ACTIONS = "get_actions"; + private static final String EXTRA_ACTIONS = "actions"; + private static final String EXTRA_ACTION = "action"; + private static final String EXTRA_MAX_NUM_ACTIONS_SHOWN = "max_num_actions_shown"; + private static final String EXTRA_PACKAGES = "packages"; + + private static WellbeingModel sInstance; + + private final Context mContext; + private final String mWellbeingProviderPkg; + private final Handler mWorkerHandler; + + private final ContentObserver mContentObserver; + + private final Object mModelLock = new Object(); + // Maps the action Id to the corresponding RemoteAction + private final Map mActionIdMap = new ArrayMap<>(); + private final Map mPackageToActionId = new HashMap<>(); + + private boolean mIsInTest; + + private WellbeingModel(final Context context) { + mContext = context; + mWorkerHandler = + new Handler(createAndStartNewLooper("WellbeingHandler"), this::handleMessage); + + mWellbeingProviderPkg = mContext.getString(R.string.wellbeing_provider_pkg); + mContentObserver = new ContentObserver(MAIN_EXECUTOR.getHandler()) { + @Override + public void onChange(boolean selfChange, Uri uri) { + // Wellbeing reports that app actions have changed. + if (DEBUG || mIsInTest) { + Log.d(TAG, "ContentObserver.onChange() called with: selfChange = [" + selfChange + + "], uri = [" + uri + "]"); + } + Preconditions.assertUIThread(); + updateWellbeingData(); + } + }; + + if (!TextUtils.isEmpty(mWellbeingProviderPkg)) { + context.registerReceiver( + new SimpleBroadcastReceiver(this::onWellbeingProviderChanged), + PackageManagerHelper.getPackageFilter(mWellbeingProviderPkg, + Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_CHANGED, + Intent.ACTION_PACKAGE_REMOVED, Intent.ACTION_PACKAGE_DATA_CLEARED, + Intent.ACTION_PACKAGE_RESTARTED)); + + IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addDataScheme("package"); + context.registerReceiver(new SimpleBroadcastReceiver(this::onAppPackageChanged), + filter); + + restartObserver(); + } + } + + public void setInTest(boolean inTest) { + mIsInTest = inTest; + } + + protected void onWellbeingProviderChanged(Intent intent) { + if (DEBUG || mIsInTest) { + Log.d(TAG, "Changes to Wellbeing package: intent = [" + intent + "]"); + } + restartObserver(); + } + + private void restartObserver() { + final ContentResolver resolver = mContext.getContentResolver(); + resolver.unregisterContentObserver(mContentObserver); + Uri actionsUri = apiBuilder().path("actions").build(); + try { + resolver.registerContentObserver( + actionsUri, true /* notifyForDescendants */, mContentObserver); + } catch (Exception e) { + Log.e(TAG, "Failed to register content observer for " + actionsUri + ": " + e); + if (mIsInTest) throw new RuntimeException(e); + } + updateWellbeingData(); + } + + @MainThread + public static WellbeingModel get(@NonNull Context context) { + Preconditions.assertUIThread(); + if (sInstance == null) { + sInstance = new WellbeingModel(context.getApplicationContext()); + } + return sInstance; + } + + @MainThread + private SystemShortcut getShortcutForApp(String packageName, int userId, + BaseDraggingActivity activity, ItemInfo info) { + Preconditions.assertUIThread(); + // Work profile apps are not recognized by digital wellbeing. + if (userId != UserHandle.myUserId()) { + if (DEBUG || mIsInTest) { + Log.d(TAG, "getShortcutForApp [" + packageName + "]: not current user"); + } + return null; + } + + synchronized (mModelLock) { + String actionId = mPackageToActionId.get(packageName); + final RemoteAction action = actionId != null ? mActionIdMap.get(actionId) : null; + if (action == null) { + if (DEBUG || mIsInTest) { + Log.d(TAG, "getShortcutForApp [" + packageName + "]: no action"); + } + return null; + } + if (DEBUG || mIsInTest) { + Log.d(TAG, + "getShortcutForApp [" + packageName + "]: action: '" + action.getTitle() + + "'"); + } + return new RemoteActionShortcut(action, activity, info); + } + } + + private void updateWellbeingData() { + mWorkerHandler.sendEmptyMessage(MSG_FULL_REFRESH); + } + + private Uri.Builder apiBuilder() { + return new Uri.Builder() + .scheme(SCHEME_CONTENT) + .authority(mWellbeingProviderPkg + ".api"); + } + + private boolean updateActions(String... packageNames) { + if (packageNames.length == 0) { + return true; + } + if (DEBUG || mIsInTest) { + Log.d(TAG, "retrieveActions() called with: packageNames = [" + String.join(", ", + packageNames) + "]"); + } + Preconditions.assertNonUiThread(); + + Uri contentUri = apiBuilder().build(); + final Bundle remoteActionBundle; + try (ContentProviderClient client = mContext.getContentResolver() + .acquireUnstableContentProviderClient(contentUri)) { + if (client == null) { + if (DEBUG || mIsInTest) Log.i(TAG, "retrieveActions(): null provider"); + return false; + } + + // Prepare wellbeing call parameters. + final Bundle params = new Bundle(); + params.putStringArray(EXTRA_PACKAGES, packageNames); + params.putInt(EXTRA_MAX_NUM_ACTIONS_SHOWN, 1); + // Perform wellbeing call . + remoteActionBundle = client.call(METHOD_GET_ACTIONS, null, params); + } catch (DeadObjectException e) { + Log.i(TAG, "retrieveActions(): DeadObjectException"); + return false; + } catch (Exception e) { + Log.e(TAG, "Failed to retrieve data from " + contentUri + ": " + e); + if (mIsInTest) throw new RuntimeException(e); + return true; + } + + synchronized (mModelLock) { + // Remove the entries for requested packages, and then update the fist with what we + // got from service + Arrays.stream(packageNames).forEach(mPackageToActionId::remove); + + // The result consists of sub-bundles, each one is per a remote action. Each sub-bundle + // has a RemoteAction and a list of packages to which the action applies. + for (String actionId : + remoteActionBundle.getStringArray(EXTRA_ACTIONS)) { + final Bundle actionBundle = remoteActionBundle.getBundle(actionId); + mActionIdMap.put(actionId, + actionBundle.getParcelable(EXTRA_ACTION)); + + final String[] packagesForAction = + actionBundle.getStringArray(EXTRA_PACKAGES); + if (DEBUG || mIsInTest) { + Log.d(TAG, "....actionId: " + actionId + ", packages: " + String.join(", ", + packagesForAction)); + } + for (String packageName : packagesForAction) { + mPackageToActionId.put(packageName, actionId); + } + } + } + if (DEBUG || mIsInTest) Log.i(TAG, "retrieveActions(): finished"); + return true; + } + + private boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_PACKAGE_REMOVED: { + String packageName = (String) msg.obj; + mWorkerHandler.removeCallbacksAndMessages(packageName); + synchronized (mModelLock) { + mPackageToActionId.remove(packageName); + } + return true; + } + case MSG_PACKAGE_ADDED: { + String packageName = (String) msg.obj; + mWorkerHandler.removeCallbacksAndMessages(packageName); + if (!updateActions(packageName)) { + scheduleRefreshRetry(msg); + } + return true; + } + + case MSG_FULL_REFRESH: { + // Remove all existing messages + mWorkerHandler.removeCallbacksAndMessages(null); + final String[] packageNames = mContext.getSystemService(LauncherApps.class) + .getActivityList(null, Process.myUserHandle()).stream() + .map(li -> li.getApplicationInfo().packageName).distinct() + .toArray(String[]::new); + if (!updateActions(packageNames)) { + scheduleRefreshRetry(msg); + } + return true; + } + } + return false; + } + + private void scheduleRefreshRetry(Message originalMsg) { + int retryCount = originalMsg.arg1; + if (retryCount >= RETRY_TIMES_MS.length) { + // To many retries, skip + return; + } + + Message msg = Message.obtain(originalMsg); + msg.arg1 = retryCount + 1; + mWorkerHandler.sendMessageDelayed(msg, RETRY_TIMES_MS[retryCount]); + } + + private void onAppPackageChanged(Intent intent) { + if (DEBUG || mIsInTest) Log.d(TAG, "Changes in apps: intent = [" + intent + "]"); + Preconditions.assertUIThread(); + + final String packageName = intent.getData().getSchemeSpecificPart(); + if (packageName == null || packageName.length() == 0) { + // they sent us a bad intent + return; + } + + final String action = intent.getAction(); + if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { + Message.obtain(mWorkerHandler, MSG_PACKAGE_REMOVED, packageName).sendToTarget(); + } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { + Message.obtain(mWorkerHandler, MSG_PACKAGE_ADDED, packageName).sendToTarget(); + } + } + + /** + * Shortcut factory for generating wellbeing action + */ + public static final SystemShortcut.Factory SHORTCUT_FACTORY = (activity, info) -> + (info.getTargetComponent() == null) ? null : WellbeingModel.get(activity) + .getShortcutForApp( + info.getTargetComponent().getPackageName(), info.user.getIdentifier(), + activity, info); +} diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java new file mode 100644 index 0000000000..965b5f0ad7 --- /dev/null +++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.uioverrides; + +import android.app.Activity; +import android.app.Person; +import android.content.pm.ShortcutInfo; +import android.util.Base64; + +import com.android.launcher3.Utilities; +import com.android.systemui.shared.system.ActivityCompat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.util.zip.Deflater; + +public class ApiWrapper { + + public static boolean dumpActivity(Activity activity, PrintWriter writer) { + if (!Utilities.IS_DEBUG_DEVICE) { + return false; + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + if (!(new ActivityCompat(activity).encodeViewHierarchy(out))) { + return false; + } + + Deflater deflater = new Deflater(); + deflater.setInput(out.toByteArray()); + deflater.finish(); + + out.reset(); + byte[] buffer = new byte[1024]; + while (!deflater.finished()) { + int count = deflater.deflate(buffer); // returns the generated code... index + out.write(buffer, 0, count); + } + + writer.println("--encoded-view-dump-v0--"); + writer.println(Base64.encodeToString( + out.toByteArray(), Base64.NO_WRAP | Base64.NO_PADDING)); + return true; + } + + public static Person[] getPersons(ShortcutInfo si) { + Person[] persons = si.getPersons(); + return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons; + } +} diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java index aa0dfc3d3b..43dc882400 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java +++ b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java @@ -16,11 +16,9 @@ package com.android.launcher3.uioverrides; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; -import com.android.launcher3.Launcher; +import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager; import com.android.launcher3.anim.AnimatorSetBuilder; @@ -30,18 +28,14 @@ import com.android.quickstep.SystemUiProxy; public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler { - private static final String TAG = "BackButtonAlphaHandler"; + private final BaseQuickstepLauncher mLauncher; - private final Launcher mLauncher; - - public BackButtonAlphaHandler(Launcher launcher) { + public BackButtonAlphaHandler(BaseQuickstepLauncher launcher) { mLauncher = launcher; } @Override - public void setState(LauncherState state) { - UiFactory.onLauncherStateOrFocusChanged(mLauncher); - } + public void setState(LauncherState state) { } @Override public void setStateWithAnimation(LauncherState toState, @@ -52,8 +46,8 @@ public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler if (!SysUINavigationMode.getMode(mLauncher).hasGestures) { // If the nav mode is not gestural, then force back button alpha to be 1 - UiThreadHelper.setBackButtonAlphaAsync(mLauncher, UiFactory.SET_BACK_BUTTON_ALPHA, 1f, - true /* animate */); + UiThreadHelper.setBackButtonAlphaAsync(mLauncher, + BaseQuickstepLauncher.SET_BACK_BUTTON_ALPHA, 1f, true /* animate */); return; } @@ -64,15 +58,8 @@ public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler anim.setDuration(config.duration); anim.addUpdateListener(valueAnimator -> { final float alpha = (float) valueAnimator.getAnimatedValue(); - UiThreadHelper.setBackButtonAlphaAsync(mLauncher, UiFactory.SET_BACK_BUTTON_ALPHA, - alpha, false /* animate */); - }); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // Reapply the final alpha in case some state (e.g. window focus) changed. - UiFactory.onLauncherStateOrFocusChanged(mLauncher); - } + UiThreadHelper.setBackButtonAlphaAsync(mLauncher, + BaseQuickstepLauncher.SET_BACK_BUTTON_ALPHA, alpha, false /* animate */); }); builder.play(anim); } diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java deleted file mode 100644 index 17c681b26f..0000000000 --- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.uioverrides; - -import static android.app.Activity.RESULT_CANCELED; - -import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; -import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON; -import static com.android.launcher3.LauncherState.ALL_APPS; -import static com.android.launcher3.LauncherState.NORMAL; -import static com.android.launcher3.LauncherState.OVERVIEW; -import static com.android.launcher3.allapps.DiscoveryBounce.BOUNCE_MAX_COUNT; -import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_COUNT; -import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN; -import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_COUNT; -import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN; - -import android.animation.AnimatorSet; -import android.animation.ValueAnimator; -import android.app.Activity; -import android.app.Person; -import android.content.Context; -import android.content.Intent; -import android.content.IntentSender; -import android.content.pm.ShortcutInfo; -import android.os.Bundle; -import android.os.CancellationSignal; -import android.util.Base64; - -import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherState; -import com.android.launcher3.LauncherState.ScaleAndTranslation; -import com.android.launcher3.LauncherStateManager; -import com.android.launcher3.LauncherStateManager.StateHandler; -import com.android.launcher3.QuickstepAppTransitionManagerImpl; -import com.android.launcher3.Utilities; -import com.android.launcher3.proxy.ProxyActivityStarter; -import com.android.launcher3.proxy.StartActivityParams; -import com.android.launcher3.util.UiThreadHelper; -import com.android.quickstep.RecentsModel; -import com.android.quickstep.SysUINavigationMode; -import com.android.quickstep.SysUINavigationMode.Mode; -import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; -import com.android.quickstep.SystemUiProxy; -import com.android.quickstep.util.RemoteFadeOutAnimationListener; -import com.android.systemui.shared.system.ActivityCompat; - -import java.io.ByteArrayOutputStream; -import java.io.PrintWriter; -import java.util.zip.Deflater; - -public class UiFactory extends RecentsUiFactory { - - /** - * Reusable command for applying the back button alpha on the background thread. - */ - public static final UiThreadHelper.AsyncCommand SET_BACK_BUTTON_ALPHA = - (context, arg1, arg2) -> { - SystemUiProxy.INSTANCE.get(context).setBackButtonAlpha(Float.intBitsToFloat(arg1), - arg2 != 0); - }; - - public static Runnable enableLiveUIChanges(Launcher launcher) { - NavigationModeChangeListener listener = m -> { - launcher.getDragLayer().recreateControllers(); - launcher.getRotationHelper().setRotationHadDifferentUI(m != Mode.NO_BUTTON); - }; - SysUINavigationMode mode = SysUINavigationMode.INSTANCE.get(launcher); - SysUINavigationMode.Mode m = mode.addModeChangeListener(listener); - launcher.getRotationHelper().setRotationHadDifferentUI(m != Mode.NO_BUTTON); - return () -> mode.removeModeChangeListener(listener); - } - - public static StateHandler[] getStateHandler(Launcher launcher) { - return new StateHandler[] { - launcher.getAllAppsController(), - launcher.getWorkspace(), - createRecentsViewStateController(launcher), - new BackButtonAlphaHandler(launcher)}; - } - - /** - * Sets the back button visibility based on the current state/window focus. - */ - public static void onLauncherStateOrFocusChanged(Launcher launcher) { - Mode mode = SysUINavigationMode.getMode(launcher); - boolean shouldBackButtonBeHidden = mode.hasGestures - && launcher != null - && launcher.getStateManager().getState().hideBackButton - && launcher.hasWindowFocus(); - if (shouldBackButtonBeHidden) { - // Show the back button if there is a floating view visible. - shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenViewWithType(launcher, - TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null; - } - UiThreadHelper.setBackButtonAlphaAsync(launcher, UiFactory.SET_BACK_BUTTON_ALPHA, - shouldBackButtonBeHidden ? 0f : 1f, true /* animate */); - if (launcher != null && launcher.getDragLayer() != null) { - launcher.getRootView().setDisallowBackGesture(shouldBackButtonBeHidden); - } - } - - public static void onCreate(Launcher launcher) { - if (!launcher.getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)) { - launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() { - @Override - public void onStateTransitionStart(LauncherState toState) { - } - - @Override - public void onStateTransitionComplete(LauncherState finalState) { - boolean swipeUpEnabled = SysUINavigationMode.INSTANCE.get(launcher).getMode() - .hasGestures; - LauncherState prevState = launcher.getStateManager().getLastState(); - - if (((swipeUpEnabled && finalState == OVERVIEW) || (!swipeUpEnabled - && finalState == ALL_APPS && prevState == NORMAL) || BOUNCE_MAX_COUNT <= - launcher.getSharedPrefs().getInt(HOME_BOUNCE_COUNT, 0))) { - launcher.getSharedPrefs().edit().putBoolean(HOME_BOUNCE_SEEN, true).apply(); - launcher.getStateManager().removeStateListener(this); - } - } - }); - } - - if (!launcher.getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)) { - launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() { - @Override - public void onStateTransitionStart(LauncherState toState) { - } - - @Override - public void onStateTransitionComplete(LauncherState finalState) { - LauncherState prevState = launcher.getStateManager().getLastState(); - - if ((finalState == ALL_APPS && prevState == OVERVIEW) || BOUNCE_MAX_COUNT <= - launcher.getSharedPrefs().getInt(SHELF_BOUNCE_COUNT, 0)) { - launcher.getSharedPrefs().edit().putBoolean(SHELF_BOUNCE_SEEN, true).apply(); - launcher.getStateManager().removeStateListener(this); - } - } - }); - } - } - - public static void onEnterAnimationComplete(Context context) { - // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled - // as a part of quickstep, so that high-res thumbnails can load the next time we enter - // overview - RecentsModel.INSTANCE.get(context).getThumbnailCache() - .getHighResLoadingState().setVisible(true); - } - - public static void onTrimMemory(Context context, int level) { - RecentsModel model = RecentsModel.INSTANCE.get(context); - if (model != null) { - model.onTrimMemory(level); - } - } - - public static void useFadeOutAnimationForLauncherStart(Launcher launcher, - CancellationSignal cancellationSignal) { - QuickstepAppTransitionManagerImpl appTransitionManager = - (QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager(); - appTransitionManager.setRemoteAnimationProvider((appTargets, wallpaperTargets) -> { - - // On the first call clear the reference. - cancellationSignal.cancel(); - - ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0); - fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets, - wallpaperTargets)); - AnimatorSet anim = new AnimatorSet(); - anim.play(fadeAnimation); - return anim; - }, cancellationSignal); - } - - public static boolean dumpActivity(Activity activity, PrintWriter writer) { - if (!Utilities.IS_DEBUG_DEVICE) { - return false; - } - ByteArrayOutputStream out = new ByteArrayOutputStream(); - if (!(new ActivityCompat(activity).encodeViewHierarchy(out))) { - return false; - } - - Deflater deflater = new Deflater(); - deflater.setInput(out.toByteArray()); - deflater.finish(); - - out.reset(); - byte[] buffer = new byte[1024]; - while (!deflater.finished()) { - int count = deflater.deflate(buffer); // returns the generated code... index - out.write(buffer, 0, count); - } - - writer.println("--encoded-view-dump-v0--"); - writer.println(Base64.encodeToString( - out.toByteArray(), Base64.NO_WRAP | Base64.NO_PADDING)); - return true; - } - - public static boolean startIntentSenderForResult(Activity activity, IntentSender intent, - int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, - Bundle options) { - StartActivityParams params = new StartActivityParams(activity, requestCode); - params.intentSender = intent; - params.fillInIntent = fillInIntent; - params.flagsMask = flagsMask; - params.flagsValues = flagsValues; - params.extraFlags = extraFlags; - params.options = options; - ((Context) activity).startActivity(ProxyActivityStarter.getLaunchIntent(activity, params)); - return true; - } - - public static boolean startActivityForResult(Activity activity, Intent intent, int requestCode, - Bundle options) { - StartActivityParams params = new StartActivityParams(activity, requestCode); - params.intent = intent; - params.options = options; - activity.startActivity(ProxyActivityStarter.getLaunchIntent(activity, params)); - return true; - } - - /** - * Removes any active ProxyActivityStarter task and sends RESULT_CANCELED to Launcher. - * - * ProxyActivityStarter is started with clear task to reset the task after which it removes the - * task itself. - */ - public static void resetPendingActivityResults(Launcher launcher, int requestCode) { - launcher.onActivityResult(requestCode, RESULT_CANCELED, null); - launcher.startActivity(ProxyActivityStarter.getLaunchIntent(launcher, null)); - } - - public static ScaleAndTranslation getOverviewScaleAndTranslationForNormalState(Launcher l) { - if (SysUINavigationMode.getMode(l) == Mode.NO_BUTTON) { - float offscreenTranslationX = l.getDeviceProfile().widthPx - - l.getOverviewPanel().getPaddingStart(); - return new ScaleAndTranslation(1f, offscreenTranslationX, 0f); - } - return new ScaleAndTranslation(1.1f, 0f, 0f); - } - - public static Person[] getPersons(ShortcutInfo si) { - Person[] persons = si.getPersons(); - return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons; - } -} diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java index bb72315d3b..3cb00885e8 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java @@ -11,10 +11,10 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager.AnimationComponents; import com.android.launcher3.touch.AbstractStateChangeTouchController; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; -import com.android.quickstep.RecentsModel; +import com.android.quickstep.SystemUiProxy; /** * Touch controller for handling edge swipes in landscape/seascape UI @@ -24,7 +24,7 @@ public class LandscapeEdgeSwipeController extends AbstractStateChangeTouchContro private static final String TAG = "LandscapeEdgeSwipeCtrl"; public LandscapeEdgeSwipeController(Launcher l) { - super(l, SwipeDetector.HORIZONTAL); + super(l, SingleAxisSwipeDetector.HORIZONTAL); } @Override @@ -73,7 +73,7 @@ public class LandscapeEdgeSwipeController extends AbstractStateChangeTouchContro protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) { super.onSwipeInteractionCompleted(targetState, logAction); if (mStartState == NORMAL && targetState == OVERVIEW) { - RecentsModel.INSTANCE.get(mLauncher).onOverviewShown(true, TAG); + SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG); } } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java index ef6a5e23d6..99b2a81386 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java @@ -43,11 +43,10 @@ import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.touch.AbstractStateChangeTouchController; -import com.android.launcher3.touch.SwipeDetector; +import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.uioverrides.states.OverviewState; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; -import com.android.quickstep.RecentsModel; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TouchInteractionService; import com.android.quickstep.util.LayoutUtils; @@ -79,7 +78,7 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr private boolean mFinishFastOnSecondTouch; public PortraitStatesTouchController(Launcher l, boolean allowDragToOverview) { - super(l, SwipeDetector.VERTICAL); + super(l, SingleAxisSwipeDetector.VERTICAL); mOverviewPortraitStateTouchHelper = new PortraitOverviewStateTouchHelper(l); mAllowDragToOverview = allowDragToOverview; } @@ -300,7 +299,7 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) { super.onSwipeInteractionCompleted(targetState, logAction); if (mStartState == NORMAL && targetState == OVERVIEW) { - RecentsModel.INSTANCE.get(mLauncher).onOverviewShown(true, TAG); + SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG); } } diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index 409bec6c70..fd55e077fd 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -32,11 +32,12 @@ import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.quickstep.util.ActivityInitListener; +import com.android.quickstep.util.ShelfPeekAnim; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; -import java.util.function.BiPredicate; import java.util.function.Consumer; +import java.util.function.Predicate; /** * Utility class which abstracts out the logical differences between Launcher and RecentsActivity. @@ -44,21 +45,26 @@ import java.util.function.Consumer; @TargetApi(Build.VERSION_CODES.P) public interface BaseActivityInterface { - void onTransitionCancelled(T activity, boolean activityVisible); + void onTransitionCancelled(boolean activityVisible); int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect); - void onSwipeUpToRecentsComplete(T activity); + void onSwipeUpToRecentsComplete(); - default void onSwipeUpToHomeComplete(T activity) { } + default void onSwipeUpToHomeComplete() { } void onAssistantVisibilityChanged(float visibility); - @NonNull HomeAnimationFactory prepareHomeUI(T activity); + @NonNull HomeAnimationFactory prepareHomeUI(); - AnimationFactory prepareRecentsUI(T activity, boolean activityVisible, - boolean animateActivity, Consumer callback); + AnimationFactory prepareRecentsUI(boolean activityVisible, boolean animateActivity, + Consumer callback); - ActivityInitListener createActivityInitListener(BiPredicate onInitListener); + ActivityInitListener createActivityInitListener(Predicate onInitListener); + + /** + * Sets a callback to be run when an activity launch happens while launcher is not yet resumed. + */ + default void setOnDeferredActivityLaunchCallback(Runnable r) {} @Nullable T getCreatedActivity(); @@ -83,6 +89,13 @@ public interface BaseActivityInterface { return true; } + /** + * Updates the prediction state to the overview state. + */ + default void updateOverviewPredictionState() { + // By default overview predictions are not supported + } + /** * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher} */ @@ -90,26 +103,17 @@ public interface BaseActivityInterface { boolean isInLiveTileMode(); - void onLaunchTaskFailed(T activity); + void onLaunchTaskFailed(); - void onLaunchTaskSuccess(T activity); + void onLaunchTaskSuccess(); default void closeOverlay() { } - default void switchToScreenshot(ThumbnailData thumbnailData, Runnable runnable) {} + default void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData, + Runnable runnable) {} interface AnimationFactory { - enum ShelfAnimState { - HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false); - - ShelfAnimState(boolean shouldPreformHaptic) { - this.shouldPreformHaptic = shouldPreformHaptic; - } - - public final boolean shouldPreformHaptic; - } - default void onRemoteAnimationReceived(RemoteAnimationTargets targets) { } void createActivityInterface(long transitionLength); @@ -118,8 +122,8 @@ public interface BaseActivityInterface { default void onTransitionCancelled() { } - default void setShelfState(ShelfAnimState animState, Interpolator interpolator, - long duration) { } + default void setShelfState(ShelfPeekAnim.ShelfAnimState animState, + Interpolator interpolator, long duration) { } /** * @param attached Whether to show RecentsView alongside the app window. If false, recents diff --git a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java index 71833ad418..5fcdc191fe 100644 --- a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java +++ b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java @@ -27,7 +27,6 @@ import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.R; -import com.android.launcher3.uioverrides.UiFactory; import com.android.launcher3.util.ActivityTracker; import com.android.launcher3.util.SystemUiController; import com.android.launcher3.util.Themes; @@ -122,13 +121,17 @@ public abstract class BaseRecentsActivity extends BaseDraggingActivity { @Override public void onEnterAnimationComplete() { super.onEnterAnimationComplete(); - UiFactory.onEnterAnimationComplete(this); + // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled + // as a part of quickstep, so that high-res thumbnails can load the next time we enter + // overview + RecentsModel.INSTANCE.get(this).getThumbnailCache() + .getHighResLoadingState().setVisible(true); } @Override public void onTrimMemory(int level) { super.onTrimMemory(level); - UiFactory.onTrimMemory(this, level); + RecentsModel.INSTANCE.get(this).onTrimMemory(level); } @Override diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java index de64227c26..ae0886b913 100644 --- a/quickstep/src/com/android/quickstep/GestureState.java +++ b/quickstep/src/com/android/quickstep/GestureState.java @@ -15,22 +15,270 @@ */ package com.android.quickstep; +import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; + +import android.app.ActivityManager; +import android.content.Intent; import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; +import com.android.systemui.shared.recents.model.ThumbnailData; +import java.util.ArrayList; /** * Manages the state for an active system gesture, listens for events from the system and Launcher, * and fires events when the states change. */ -public class GestureState { +public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationListener { - // Needed to interact with the current activity - private BaseActivityInterface mActivityInterface; + /** + * Defines the end targets of a gesture and the associated state. + */ + public enum GestureEndTarget { + HOME(true, ContainerType.WORKSPACE, false), - public GestureState(BaseActivityInterface activityInterface) { - mActivityInterface = activityInterface; + RECENTS(true, ContainerType.TASKSWITCHER, true), + + NEW_TASK(false, ContainerType.APP, true), + + LAST_TASK(false, ContainerType.APP, false); + + GestureEndTarget(boolean isLauncher, int containerType, + boolean recentsAttachedToAppWindow) { + this.isLauncher = isLauncher; + this.containerType = containerType; + this.recentsAttachedToAppWindow = recentsAttachedToAppWindow; + } + + /** Whether the target is in the launcher activity. Implicitly, if the end target is going + to Launcher, then we can not interrupt the animation to start another gesture. */ + public final boolean isLauncher; + /** Used to log where the user ended up after the gesture ends */ + public final int containerType; + /** Whether RecentsView should be attached to the window as we animate to this target */ + public final boolean recentsAttachedToAppWindow; } + private static final String TAG = "GestureState"; + + private static final ArrayList STATE_NAMES = new ArrayList<>(); + private static int FLAG_COUNT = 0; + private static int getFlagForIndex(String name) { + if (DEBUG_STATES) { + STATE_NAMES.add(name); + } + int index = 1 << FLAG_COUNT; + FLAG_COUNT++; + return index; + } + + // Called when the end target as been set + public static final int STATE_END_TARGET_SET = + getFlagForIndex("STATE_END_TARGET_SET"); + + // Called when the end target animation has finished + public static final int STATE_END_TARGET_ANIMATION_FINISHED = + getFlagForIndex("STATE_END_TARGET_ANIMATION_FINISHED"); + + // Called when the recents animation has been requested to start + public static final int STATE_RECENTS_ANIMATION_INITIALIZED = + getFlagForIndex("STATE_RECENTS_ANIMATION_INITIALIZED"); + + // Called when the recents animation is started and the TaskAnimationManager has been updated + // with the controller and targets + public static final int STATE_RECENTS_ANIMATION_STARTED = + getFlagForIndex("STATE_RECENTS_ANIMATION_STARTED"); + + // Called when the recents animation is canceled + public static final int STATE_RECENTS_ANIMATION_CANCELED = + getFlagForIndex("STATE_RECENTS_ANIMATION_CANCELED"); + + // Called when the recents animation finishes + public static final int STATE_RECENTS_ANIMATION_FINISHED = + getFlagForIndex("STATE_RECENTS_ANIMATION_FINISHED"); + + // Always called when the recents animation ends (regardless of cancel or finish) + public static final int STATE_RECENTS_ANIMATION_ENDED = + getFlagForIndex("STATE_RECENTS_ANIMATION_ENDED"); + + + // Needed to interact with the current activity + private final Intent mHomeIntent; + private final Intent mOverviewIntent; + private final BaseActivityInterface mActivityInterface; + private final MultiStateCallback mStateCallback; + private final int mGestureId; + + private ActivityManager.RunningTaskInfo mRunningTask; + private GestureEndTarget mEndTarget; + // TODO: This can be removed once we stop finishing the animation when starting a new task + private int mFinishingRecentsAnimationTaskId = -1; + + public GestureState(OverviewComponentObserver componentObserver, int gestureId) { + mHomeIntent = componentObserver.getHomeIntent(); + mOverviewIntent = componentObserver.getOverviewIntent(); + mActivityInterface = componentObserver.getActivityInterface(); + mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0])); + mGestureId = gestureId; + } + + public GestureState() { + // Do nothing, only used for initializing the gesture state prior to user unlock + mHomeIntent = new Intent(); + mOverviewIntent = new Intent(); + mActivityInterface = null; + mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0])); + mGestureId = -1; + } + + /** + * @return whether the gesture state has the provided {@param stateMask} flags set. + */ + public boolean hasState(int stateMask) { + return mStateCallback.hasStates(stateMask); + } + + /** + * Sets the given {@param stateFlag}s. + */ + public void setState(int stateFlag) { + mStateCallback.setState(stateFlag); + } + + /** + * Adds a callback for when the states matching the given {@param stateMask} is set. + */ + public void runOnceAtState(int stateMask, Runnable callback) { + mStateCallback.runOnceAtState(stateMask, callback); + } + + /** + * @return the intent for the Home component. + */ + public Intent getHomeIntent() { + return mHomeIntent; + } + + /** + * @return the intent for the Overview component. + */ + public Intent getOverviewIntent() { + return mOverviewIntent; + } + + /** + * @return the interface to the activity handing the UI updates for this gesture. + */ public BaseActivityInterface getActivityInterface() { return mActivityInterface; } + + /** + * @return the id for this particular gesture. + */ + public int getGestureId() { + return mGestureId; + } + + /** + * @return the running task for this gesture. + */ + public ActivityManager.RunningTaskInfo getRunningTask() { + return mRunningTask; + } + + /** + * @return the running task id for this gesture. + */ + public int getRunningTaskId() { + return mRunningTask != null ? mRunningTask.taskId : -1; + } + + /** + * Updates the running task for the gesture to be the given {@param runningTask}. + */ + public void updateRunningTask(ActivityManager.RunningTaskInfo runningTask) { + mRunningTask = runningTask; + } + + /** + * @return the end target for this gesture (if known). + */ + public GestureEndTarget getEndTarget() { + return mEndTarget; + } + + /** + * Sets the end target of this gesture and immediately notifies the state changes. + */ + public void setEndTarget(GestureEndTarget target) { + setEndTarget(target, true /* isAtomic */); + } + + /** + * Sets the end target of this gesture, but if {@param isAtomic} is {@code false}, then the + * caller must explicitly set {@link #STATE_END_TARGET_ANIMATION_FINISHED} themselves. + */ + public void setEndTarget(GestureEndTarget target, boolean isAtomic) { + mEndTarget = target; + mStateCallback.setState(STATE_END_TARGET_SET); + if (isAtomic) { + mStateCallback.setState(STATE_END_TARGET_ANIMATION_FINISHED); + } + } + + /** + * @return the id for the task that was about to be launched following the finish of the recents + * animation. Only defined between when the finish-recents call was made and the launch + * activity call is made. + */ + public int getFinishingRecentsAnimationTaskId() { + return mFinishingRecentsAnimationTaskId; + } + + /** + * Sets the id for the task will be launched after the recents animation is finished. Once the + * animation has finished then the id will be reset to -1. + */ + public void setFinishingRecentsAnimationTaskId(int taskId) { + mFinishingRecentsAnimationTaskId = taskId; + mStateCallback.runOnceAtState(STATE_RECENTS_ANIMATION_FINISHED, () -> { + mFinishingRecentsAnimationTaskId = -1; + }); + } + + /** + * @return whether the current gesture is still running a recents animation to a state in the + * Launcher or Recents activity. + * Updates the running task for the gesture to be the given {@param runningTask}. + */ + public boolean isRunningAnimationToLauncher() { + return isRecentsAnimationRunning() && mEndTarget != null && mEndTarget.isLauncher; + } + + /** + * @return whether the recents animation is started but not yet ended + */ + public boolean isRecentsAnimationRunning() { + return mStateCallback.hasStates(STATE_RECENTS_ANIMATION_INITIALIZED) && + !mStateCallback.hasStates(STATE_RECENTS_ANIMATION_ENDED); + } + + @Override + public void onRecentsAnimationStart(RecentsAnimationController controller, + RecentsAnimationTargets targets) { + mStateCallback.setState(STATE_RECENTS_ANIMATION_STARTED); + } + + @Override + public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) { + mStateCallback.setState(STATE_RECENTS_ANIMATION_CANCELED); + mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED); + } + + @Override + public void onRecentsAnimationFinished(RecentsAnimationController controller) { + mStateCallback.setState(STATE_RECENTS_ANIMATION_FINISHED); + mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED); + } } diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java index 62c0ded534..3e84e7d5ac 100644 --- a/quickstep/src/com/android/quickstep/InputConsumer.java +++ b/quickstep/src/com/android/quickstep/InputConsumer.java @@ -33,7 +33,7 @@ public interface InputConsumer { int TYPE_SCREEN_PINNED = 1 << 6; int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7; int TYPE_RESET_GESTURE = 1 << 8; - int TYPE_QUICK_CAPTURE = 1 << 9; + int TYPE_OVERSCROLL = 1 << 9; String[] NAMES = new String[] { "TYPE_NO_OP", // 0 @@ -45,17 +45,13 @@ public interface InputConsumer { "TYPE_SCREEN_PINNED", // 6 "TYPE_OVERVIEW_WITHOUT_FOCUS", // 7 "TYPE_RESET_GESTURE", // 8 - "TYPE_QUICK_CAPTURE", // 9 + "TYPE_OVERSCROLL", // 9 }; InputConsumer NO_OP = () -> TYPE_NO_OP; int getType(); - default boolean useSharedSwipeState() { - return false; - } - /** * Returns true if the user has crossed the threshold for it to be an explicit action. */ @@ -65,6 +61,8 @@ public interface InputConsumer { /** * Called by the event queue when the consumer is about to be switched to a new consumer. + * Consumers should update the state accordingly here before the state is passed to the new + * consumer. */ default void onConsumerAboutToBeSwitched() { } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java similarity index 50% rename from quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java rename to quickstep/src/com/android/quickstep/MultiStateCallback.java index 357c9fc35e..6c65e01c20 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java +++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java @@ -15,11 +15,17 @@ */ package com.android.quickstep; +import static com.android.launcher3.Utilities.postAsyncCallback; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + +import android.os.Looper; import android.util.Log; import android.util.SparseArray; import com.android.launcher3.config.FeatureFlags; +import java.util.ArrayList; +import java.util.LinkedList; import java.util.StringJoiner; import java.util.function.Consumer; @@ -31,16 +37,29 @@ public class MultiStateCallback { private static final String TAG = "MultiStateCallback"; public static final boolean DEBUG_STATES = false; - private final SparseArray mCallbacks = new SparseArray<>(); - private final SparseArray> mStateChangeHandlers = new SparseArray<>(); + private final SparseArray> mCallbacks = new SparseArray<>(); + private final SparseArray>> mStateChangeListeners = + new SparseArray<>(); private final String[] mStateNames; + private int mState = 0; + public MultiStateCallback(String[] stateNames) { mStateNames = DEBUG_STATES ? stateNames : null; } - private int mState = 0; + /** + * Adds the provided state flags to the global state on the UI thread and executes any callbacks + * as a result. + */ + public void setStateOnUiThread(int stateFlag) { + if (Looper.myLooper() == Looper.getMainLooper()) { + setState(stateFlag); + } else { + postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> setState(stateFlag)); + } + } /** * Adds the provided state flags to the global state and executes any callbacks as a result. @@ -51,7 +70,7 @@ public class MultiStateCallback { + convertToFlagNames(stateFlag) + " to " + convertToFlagNames(mState)); } - int oldState = mState; + final int oldState = mState; mState = mState | stateFlag; int count = mCallbacks.size(); @@ -59,15 +78,13 @@ public class MultiStateCallback { int state = mCallbacks.keyAt(i); if ((mState & state) == state) { - Runnable callback = mCallbacks.valueAt(i); - if (callback != null) { - // Set the callback to null, so that it does not run again. - mCallbacks.setValueAt(i, null); - callback.run(); + LinkedList callbacks = mCallbacks.valueAt(i); + while (!callbacks.isEmpty()) { + callbacks.pollFirst().run(); } } } - notifyStateChangeHandlers(oldState); + notifyStateChangeListeners(oldState); } /** @@ -82,38 +99,61 @@ public class MultiStateCallback { int oldState = mState; mState = mState & ~stateFlag; - notifyStateChangeHandlers(oldState); + notifyStateChangeListeners(oldState); } - private void notifyStateChangeHandlers(int oldState) { - int count = mStateChangeHandlers.size(); + private void notifyStateChangeListeners(int oldState) { + int count = mStateChangeListeners.size(); for (int i = 0; i < count; i++) { - int state = mStateChangeHandlers.keyAt(i); + int state = mStateChangeListeners.keyAt(i); boolean wasOn = (state & oldState) == state; boolean isOn = (state & mState) == state; if (wasOn != isOn) { - mStateChangeHandlers.valueAt(i).accept(isOn); + ArrayList> listeners = mStateChangeListeners.valueAt(i); + for (Consumer listener : listeners) { + listener.accept(isOn); + } } } } /** - * Sets the callbacks to be run when the provided states are enabled. - * The callback is only run once. + * Sets a callback to be run when the provided states in the given {@param stateMask} is + * enabled. The callback is only run *once*, and if the states are already set at the time of + * this call then the callback will be made immediately. */ - public void addCallback(int stateMask, Runnable callback) { - if (FeatureFlags.IS_DOGFOOD_BUILD && mCallbacks.get(stateMask) != null) { - throw new IllegalStateException("Multiple callbacks on same state"); + public void runOnceAtState(int stateMask, Runnable callback) { + if ((mState & stateMask) == stateMask) { + callback.run(); + } else { + final LinkedList callbacks; + if (mCallbacks.indexOfKey(stateMask) >= 0) { + callbacks = mCallbacks.get(stateMask); + if (FeatureFlags.IS_DOGFOOD_BUILD && callbacks.contains(callback)) { + throw new IllegalStateException("Existing callback for state found"); + } + } else { + callbacks = new LinkedList<>(); + mCallbacks.put(stateMask, callbacks); + } + callbacks.add(callback); } - mCallbacks.put(stateMask, callback); } /** - * Sets the handler to be called when the provided states are enabled or disabled. + * Adds a persistent listener to be called states in the given {@param stateMask} are enabled + * or disabled. */ - public void addChangeHandler(int stateMask, Consumer handler) { - mStateChangeHandlers.put(stateMask, handler); + public void addChangeListener(int stateMask, Consumer listener) { + final ArrayList> listeners; + if (mStateChangeListeners.indexOfKey(stateMask) >= 0) { + listeners = mStateChangeListeners.get(stateMask); + } else { + listeners = new ArrayList<>(); + mStateChangeListeners.put(stateMask, listeners); + } + listeners.add(listener); } public int getState() { diff --git a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java deleted file mode 100644 index bd6204aab1..0000000000 --- a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.quickstep; - -import android.annotation.TargetApi; -import android.app.ActivityManager.TaskDescription; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.UserHandle; -import android.util.LruCache; -import android.util.SparseArray; - -import com.android.launcher3.FastBitmapDrawable; -import com.android.launcher3.icons.BitmapInfo; -import com.android.launcher3.graphics.DrawableFactory; -import com.android.launcher3.icons.LauncherIcons; -import com.android.systemui.shared.recents.model.IconLoader; -import com.android.systemui.shared.recents.model.TaskKeyLruCache; - -/** - * Extension of {@link IconLoader} with icon normalization support - */ -@TargetApi(Build.VERSION_CODES.O) -public class NormalizedIconLoader extends IconLoader { - - private final SparseArray mDefaultIcons = new SparseArray<>(); - private final DrawableFactory mDrawableFactory; - private final boolean mDisableColorExtraction; - - public NormalizedIconLoader(Context context, TaskKeyLruCache iconCache, - LruCache activityInfoCache, - boolean disableColorExtraction) { - super(context, iconCache, activityInfoCache); - mDrawableFactory = DrawableFactory.INSTANCE.get(context); - mDisableColorExtraction = disableColorExtraction; - } - - @Override - public Drawable getDefaultIcon(int userId) { - synchronized (mDefaultIcons) { - BitmapInfo info = mDefaultIcons.get(userId); - if (info == null) { - info = getBitmapInfo(Resources.getSystem() - .getDrawable(android.R.drawable.sym_def_app_icon), userId, 0, false); - mDefaultIcons.put(userId, info); - } - - return new FastBitmapDrawable(info); - } - } - - @Override - protected Drawable createBadgedDrawable(Drawable drawable, int userId, TaskDescription desc) { - return new FastBitmapDrawable(getBitmapInfo(drawable, userId, desc.getPrimaryColor(), - false)); - } - - private BitmapInfo getBitmapInfo(Drawable drawable, int userId, - int primaryColor, boolean isInstantApp) { - try (LauncherIcons la = LauncherIcons.obtain(mContext)) { - if (mDisableColorExtraction) { - la.disableColorExtraction(); - } - la.setWrapperBackgroundColor(primaryColor); - - // User version code O, so that the icon is always wrapped in an adaptive icon container - return la.createBadgedIconBitmap(drawable, UserHandle.of(userId), - Build.VERSION_CODES.O, isInstantApp); - } - } - - @Override - protected Drawable getBadgedActivityIcon(ActivityInfo activityInfo, int userId, - TaskDescription desc) { - BitmapInfo bitmapInfo = getBitmapInfo( - activityInfo.loadUnbadgedIcon(mContext.getPackageManager()), - userId, - desc.getPrimaryColor(), - activityInfo.applicationInfo.isInstantApp()); - return mDrawableFactory.newIcon(mContext, bitmapInfo, activityInfo); - } -} diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java index 2918879d74..acf61b4142 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java @@ -127,7 +127,7 @@ public class RecentsAnimationCallbacks implements */ public interface RecentsAnimationListener { default void onRecentsAnimationStart(RecentsAnimationController controller, - RecentsAnimationTargets targetSet) {} + RecentsAnimationTargets targets) {} /** * Callback from the system when the recents animation is canceled. {@param thumbnailData} @@ -135,6 +135,9 @@ public class RecentsAnimationCallbacks implements */ default void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {} + /** + * Callback made whenever the recents animation is finished. + */ default void onRecentsAnimationFinished(RecentsAnimationController controller) {} } } diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java index d938dc5af8..46af8bfcd3 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java @@ -18,6 +18,7 @@ package com.android.quickstep; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; + import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; @@ -48,11 +49,10 @@ public class RecentsAnimationController { private final Consumer mOnFinishedListener; private final boolean mShouldMinimizeSplitScreen; - private boolean mWindowThresholdCrossed = false; - private InputConsumerController mInputConsumerController; private Supplier mInputProxySupplier; private InputConsumer mInputConsumer; + private boolean mWindowThresholdCrossed = false; private boolean mTouchInProgress; private boolean mFinishPending; @@ -62,8 +62,6 @@ public class RecentsAnimationController { mController = controller; mOnFinishedListener = onFinishedListener; mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen; - - setWindowThresholdCrossed(mWindowThresholdCrossed); } /** @@ -71,7 +69,7 @@ public class RecentsAnimationController { * currently being animated. */ public ThumbnailData screenshotTask(int taskId) { - return mController != null ? mController.screenshotTask(taskId) : null; + return mController.screenshotTask(taskId); } /** @@ -188,6 +186,11 @@ public class RecentsAnimationController { mInputConsumerController.setInputListener(this::onInputConsumerEvent); } + /** @return wrapper controller. */ + public RecentsAnimationControllerCompat getController() { + return mController; + } + private void disableInputProxy() { if (mInputConsumer != null && mTouchInProgress) { long now = SystemClock.uptimeMillis(); diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java index 9b094f6e2d..81f411e4e6 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java @@ -16,10 +16,14 @@ package com.android.quickstep; import static android.content.Intent.ACTION_USER_UNLOCKED; + +import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED; import static com.android.launcher3.ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE; import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS; +import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; @@ -33,27 +37,37 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_S import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; +import android.database.ContentObserver; import android.graphics.Point; import android.graphics.RectF; import android.graphics.Region; import android.os.Process; +import android.provider.Settings; import android.text.TextUtils; +import android.util.Log; import android.view.MotionEvent; import android.view.Surface; + import androidx.annotation.BinderThread; + import com.android.launcher3.R; import com.android.launcher3.ResourceUtils; import com.android.launcher3.Utilities; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.DefaultDisplay; +import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; +import com.android.quickstep.util.NavBarPosition; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags; import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat; + import java.io.PrintWriter; import java.util.ArrayList; @@ -61,17 +75,20 @@ import java.util.ArrayList; * Manages the state of the system during a swipe up gesture. */ public class RecentsAnimationDeviceState implements - SysUINavigationMode.NavigationModeChangeListener, + NavigationModeChangeListener, DefaultDisplay.DisplayInfoChangeListener { - private Context mContext; - private UserManagerCompat mUserManager; - private SysUINavigationMode mSysUiNavMode; - private DefaultDisplay mDefaultDisplay; - private int mDisplayId; + private final Context mContext; + private final UserManagerCompat mUserManager; + private final SysUINavigationMode mSysUiNavMode; + private final DefaultDisplay mDefaultDisplay; + private final int mDisplayId; + + private final ArrayList mOnDestroyActions = new ArrayList<>(); private @SystemUiStateFlags int mSystemUiStateFlags; private SysUINavigationMode.Mode mMode = THREE_BUTTONS; + private NavBarPosition mNavBarPosition; private final RectF mSwipeUpTouchRegion = new RectF(); private final Region mDeferredGestureRegion = new Region(); @@ -98,11 +115,13 @@ public class RecentsAnimationDeviceState implements private ComponentName mGestureBlockedActivity; public RecentsAnimationDeviceState(Context context) { + final ContentResolver resolver = context.getContentResolver(); mContext = context; mUserManager = UserManagerCompat.getInstance(context); mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context); mDefaultDisplay = DefaultDisplay.INSTANCE.get(context); mDisplayId = mDefaultDisplay.getInfo().id; + runOnDestroy(() -> mDefaultDisplay.removeChangeListener(this)); // Register for user unlocked if necessary mIsUserUnlocked = mUserManager.isUserUnlocked(Process.myUserHandle()); @@ -110,6 +129,7 @@ public class RecentsAnimationDeviceState implements mContext.registerReceiver(mUserUnlockedReceiver, new IntentFilter(ACTION_USER_UNLOCKED)); } + runOnDestroy(() -> Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver)); // Register for exclusion updates mExclusionListener = new SystemGestureExclusionListenerCompat(mDisplayId) { @@ -120,7 +140,11 @@ public class RecentsAnimationDeviceState implements mExclusionRegion = region; } }; + runOnDestroy(mExclusionListener::unregister); + + // Register for navigation mode changes onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this)); + runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this)); // Add any blocked activities String blockingActivity = context.getString(R.string.gesture_blocking_activity); @@ -129,18 +153,33 @@ public class RecentsAnimationDeviceState implements } } + private void runOnDestroy(Runnable action) { + mOnDestroyActions.add(action); + } + /** * Cleans up all the registered listeners and receivers. */ public void destroy() { - Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver); - mSysUiNavMode.removeModeChangeListener(this); - mDefaultDisplay.removeChangeListener(this); - mExclusionListener.unregister(); + for (Runnable r : mOnDestroyActions) { + r.run(); + } + } + + /** + * Adds a listener for the nav mode change, guaranteed to be called after the device state's + * mode has changed. + */ + public void addNavigationModeChangedCallback(NavigationModeChangeListener listener) { + listener.onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(listener)); + runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(listener)); } @Override public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "onNavigationModeChanged " + newMode); + } mDefaultDisplay.removeChangeListener(this); if (newMode.hasGestures) { mDefaultDisplay.addChangeListener(this); @@ -152,6 +191,7 @@ public class RecentsAnimationDeviceState implements mExclusionListener.unregister(); } mMode = newMode; + mNavBarPosition = new NavBarPosition(mMode, mDefaultDisplay.getInfo()); } @Override @@ -160,9 +200,45 @@ public class RecentsAnimationDeviceState implements return; } + mNavBarPosition = new NavBarPosition(mMode, info); updateGestureTouchRegions(); } + /** + * @return the current navigation mode for the device. + */ + public SysUINavigationMode.Mode getNavMode() { + return mMode; + } + + /** + * @return the nav bar position for the current nav bar mode and display rotation. + */ + public NavBarPosition getNavBarPosition() { + return mNavBarPosition; + } + + /** + * @return whether the current nav mode is fully gestural. + */ + public boolean isFullyGesturalNavMode() { + return mMode == NO_BUTTON; + } + + /** + * @return whether the current nav mode has some gestures (either 2 or 0 button mode). + */ + public boolean isGesturalNavMode() { + return mMode == TWO_BUTTONS || mMode == NO_BUTTON; + } + + /** + * @return whether the current nav mode is button-based. + */ + public boolean isButtonNavMode() { + return mMode == THREE_BUTTONS; + } + /** * @return the display id for the display that Launcher is running on. */ @@ -201,7 +277,7 @@ public class RecentsAnimationDeviceState implements * @return whether the given running task info matches the gesture-blocked activity. */ public boolean isGestureBlockedActivity(ActivityManager.RunningTaskInfo runningTaskInfo) { - return runningTaskInfo != null + return runningTaskInfo != null && mGestureBlockedActivity != null && mGestureBlockedActivity.equals(runningTaskInfo.topActivity); } diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java index 93537597d5..718c5baa2c 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java @@ -41,13 +41,4 @@ public class RecentsAnimationTargets extends RemoteAnimationTargets { public boolean hasTargets() { return unfilteredApps.length != 0; } - - /** - * Clones the target set without any actual targets. Used only when continuing a gesture after - * the actual recents animation has finished. - */ - public RecentsAnimationTargets cloneWithoutTargets() { - return new RecentsAnimationTargets(new RemoteAnimationTargetCompat[0], - new RemoteAnimationTargetCompat[0], homeContentInsets, minimizedHomeBounds); - } } diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java index 465d4648e6..517501ad9c 100644 --- a/quickstep/src/com/android/quickstep/RecentsModel.java +++ b/quickstep/src/com/android/quickstep/RecentsModel.java @@ -25,12 +25,12 @@ import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.ComponentCallbacks2; import android.content.Context; -import android.content.pm.LauncherApps; import android.os.Build; import android.os.Looper; import android.os.Process; import android.os.UserHandle; +import com.android.launcher3.icons.IconProvider; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -48,13 +48,11 @@ import java.util.function.Consumer; @TargetApi(Build.VERSION_CODES.O) public class RecentsModel extends TaskStackChangeListener { - private static final String TAG = "RecentsModel"; - // We do not need any synchronization for this variable as its only written on UI thread. public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(RecentsModel::new); - private final List mThumbnailChangeListeners = new ArrayList<>(); + private final List mThumbnailChangeListeners = new ArrayList<>(); private final Context mContext; private final RecentTasksList mTaskList; @@ -69,8 +67,10 @@ public class RecentsModel extends TaskStackChangeListener { new KeyguardManagerCompat(context), ActivityManagerWrapper.getInstance()); mIconCache = new TaskIconCache(context, looper); mThumbnailCache = new TaskThumbnailCache(context, looper); + ActivityManagerWrapper.getInstance().registerTaskStackListener(this); - setupPackageListener(); + IconProvider.registerIconChangeListener(context, + this::onPackageIconChanged, MAIN_EXECUTOR.getHandler()); } public TaskIconCache getIconCache() { @@ -183,45 +183,40 @@ public class RecentsModel extends TaskStackChangeListener { } } - public void onOverviewShown(boolean fromHome, String tag) { - SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(fromHome, tag); + private void onPackageIconChanged(String pkg, UserHandle user) { + mIconCache.invalidateCacheEntries(pkg, user); + for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) { + mThumbnailChangeListeners.get(i).onTaskIconChanged(pkg, user); + } } - private void setupPackageListener() { - mContext.getSystemService(LauncherApps.class).registerCallback(new LauncherApps.Callback() { - @Override - public void onPackageRemoved(String packageName, UserHandle user) { - mIconCache.invalidatePackage(packageName); - } - - @Override - public void onPackageChanged(String packageName, UserHandle user) { - mIconCache.invalidatePackage(packageName); - } - - @Override - public void onPackageAdded(String packageName, UserHandle user) { } - - @Override - public void onPackagesAvailable( - String[] packageNames, UserHandle user, boolean replacing) { } - - @Override - public void onPackagesUnavailable( - String[] packageNames, UserHandle user, boolean replacing) { } - }); - } - - public void addThumbnailChangeListener(TaskThumbnailChangeListener listener) { + /** + * Adds a listener for visuals changes + */ + public void addThumbnailChangeListener(TaskVisualsChangeListener listener) { mThumbnailChangeListeners.add(listener); } - public void removeThumbnailChangeListener(TaskThumbnailChangeListener listener) { + /** + * Removes a previously added listener + */ + public void removeThumbnailChangeListener(TaskVisualsChangeListener listener) { mThumbnailChangeListeners.remove(listener); } - public interface TaskThumbnailChangeListener { + /** + * Listener for receiving various task properties changes + */ + public interface TaskVisualsChangeListener { + /** + * Called whn the task thumbnail changes + */ Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData); + + /** + * Called when the icon for a task changes + */ + void onTaskIconChanged(String pkg, UserHandle user); } } diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java new file mode 100644 index 0000000000..e3e8ace96b --- /dev/null +++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep; + +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED; + +import android.content.Intent; +import android.util.Log; + +import androidx.annotation.UiThread; + +import com.android.launcher3.Utilities; +import com.android.launcher3.config.FeatureFlags; +import com.android.systemui.shared.recents.model.ThumbnailData; +import com.android.systemui.shared.system.ActivityManagerWrapper; + +public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener { + + private RecentsAnimationController mController; + private RecentsAnimationCallbacks mCallbacks; + private RecentsAnimationTargets mTargets; + // Temporary until we can hook into gesture state events + private GestureState mLastGestureState; + + /** + * Preloads the recents animation. + */ + public void preloadRecentsAnimation(Intent intent) { + // Pass null animation handler to indicate this start is for preloading + UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance() + .startRecentsActivity(intent, null, null, null, null)); + } + + /** + * Starts a new recents animation for the activity with the given {@param intent}. + */ + @UiThread + public RecentsAnimationCallbacks startRecentsAnimation(GestureState gestureState, + Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener) { + // Notify if recents animation is still running + if (mController != null) { + String msg = "New recents animation started before old animation completed"; + if (FeatureFlags.IS_DOGFOOD_BUILD) { + throw new IllegalArgumentException(msg); + } else { + Log.e("TaskAnimationManager", msg, new Exception()); + } + } + // But force-finish it anyways + finishRunningRecentsAnimation(false /* toHome */); + + final BaseActivityInterface activityInterface = gestureState.getActivityInterface(); + mLastGestureState = gestureState; + mCallbacks = new RecentsAnimationCallbacks(activityInterface.shouldMinimizeSplitScreen()); + mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() { + @Override + public void onRecentsAnimationStart(RecentsAnimationController controller, + RecentsAnimationTargets targets) { + mController = controller; + mTargets = targets; + } + + @Override + public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) { + if (thumbnailData != null) { + // If a screenshot is provided, switch to the screenshot before cleaning up + activityInterface.switchRunningTaskViewToScreenshot(thumbnailData, + () -> cleanUpRecentsAnimation(thumbnailData)); + } else { + cleanUpRecentsAnimation(null /* canceledThumbnail */); + } + } + + @Override + public void onRecentsAnimationFinished(RecentsAnimationController controller) { + cleanUpRecentsAnimation(null /* canceledThumbnail */); + } + }); + mCallbacks.addListener(gestureState); + mCallbacks.addListener(listener); + UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance() + .startRecentsActivity(intent, null, mCallbacks, null, null)); + gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED); + return mCallbacks; + } + + /** + * Continues the existing running recents animation for a new gesture. + */ + public RecentsAnimationCallbacks continueRecentsAnimation(GestureState gestureState) { + mCallbacks.removeListener(mLastGestureState); + mLastGestureState = gestureState; + mCallbacks.addListener(gestureState); + return mCallbacks; + } + + /** + * Finishes the running recents animation. + */ + public void finishRunningRecentsAnimation(boolean toHome) { + if (mController != null) { + mCallbacks.notifyAnimationCanceled(); + Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome + ? mController::finishAnimationToHome + : mController::finishAnimationToApp); + cleanUpRecentsAnimation(null /* canceledThumbnail */); + } + } + + /** + * Used to notify a listener of the current recents animation state (used if the listener was + * not yet added to the callbacks at the point that the listener callbacks would have been + * made). + */ + public void notifyRecentsAnimationState( + RecentsAnimationCallbacks.RecentsAnimationListener listener) { + if (isRecentsAnimationRunning()) { + listener.onRecentsAnimationStart(mController, mTargets); + } + // TODO: Do we actually need to report canceled/finished? + } + + /** + * @return whether there is a recents animation running. + */ + public boolean isRecentsAnimationRunning() { + return mController != null; + } + + /** + * Cleans up the recents animation entirely. + */ + private void cleanUpRecentsAnimation(ThumbnailData canceledThumbnail) { + // Clean up the screenshot if necessary + if (mController != null && canceledThumbnail != null) { + mController.cleanupScreenshot(); + } + + // Release all the target leashes + if (mTargets != null) { + mTargets.release(); + } + + // Remove gesture state from callbacks + if (mCallbacks != null && mLastGestureState != null) { + mCallbacks.removeListener(mLastGestureState); + } + + mController = null; + mCallbacks = null; + mTargets = null; + mLastGestureState = null; + } + + public void dump() { + // TODO + } +} diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java index 289a129706..e590aea4f6 100644 --- a/quickstep/src/com/android/quickstep/TaskIconCache.java +++ b/quickstep/src/com/android/quickstep/TaskIconCache.java @@ -15,67 +15,64 @@ */ package com.android.quickstep; -import static com.android.launcher3.uioverrides.RecentsUiFactory.GO_LOW_RAM_RECENTS_ENABLED; +import static com.android.launcher3.FastBitmapDrawable.newIcon; +import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import android.content.ComponentName; +import android.app.ActivityManager.TaskDescription; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Handler; import android.os.Looper; -import android.util.LruCache; +import android.os.UserHandle; +import android.util.SparseArray; import android.view.accessibility.AccessibilityManager; +import androidx.annotation.WorkerThread; + +import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.IconProvider; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.icons.cache.HandlerRunnable; import com.android.launcher3.util.Preconditions; +import com.android.quickstep.util.TaskKeyLruCache; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task.TaskKey; -import com.android.systemui.shared.recents.model.TaskKeyLruCache; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.PackageManagerWrapper; -import java.util.Map; import java.util.function.Consumer; /** * Manages the caching of task icons and related data. - * TODO(b/138944598): This class should later be merged into IconCache. */ public class TaskIconCache { private final Handler mBackgroundHandler; private final AccessibilityManager mAccessibilityManager; - private final NormalizedIconLoader mIconLoader; - - private final TaskKeyLruCache mIconCache; - private final TaskKeyLruCache mContentDescriptionCache; - private final LruCache mActivityInfoCache; - - private TaskKeyLruCache.EvictionCallback mClearActivityInfoOnEviction = - new TaskKeyLruCache.EvictionCallback() { - @Override - public void onEntryEvicted(Task.TaskKey key) { - if (key != null) { - mActivityInfoCache.remove(key.getComponent()); - } - } - }; + private final Context mContext; + private final TaskKeyLruCache mIconCache; + private final SparseArray mDefaultIcons = new SparseArray<>(); + private final IconProvider mIconProvider; public TaskIconCache(Context context, Looper backgroundLooper) { + mContext = context; mBackgroundHandler = new Handler(backgroundLooper); mAccessibilityManager = context.getSystemService(AccessibilityManager.class); Resources res = context.getResources(); int cacheSize = res.getInteger(R.integer.recentsIconCacheSize); - mIconCache = new TaskKeyLruCache<>(cacheSize, mClearActivityInfoOnEviction); - mContentDescriptionCache = new TaskKeyLruCache<>(cacheSize, mClearActivityInfoOnEviction); - mActivityInfoCache = new LruCache<>(cacheSize); - mIconLoader = new NormalizedIconLoader(context, mIconCache, mActivityInfoCache, - true /* disableColorExtraction */); + mIconCache = new TaskKeyLruCache<>(cacheSize); + mIconProvider = new IconProvider(context); } /** @@ -96,15 +93,14 @@ public class TaskIconCache { IconLoadRequest request = new IconLoadRequest(mBackgroundHandler) { @Override public void run() { - Drawable icon = mIconLoader.getIcon(task); - String contentDescription = loadContentDescriptionInBackground(task); + TaskCacheEntry entry = getCacheEntry(task); if (isCanceled()) { // We don't call back to the provided callback in this case return; } MAIN_EXECUTOR.execute(() -> { - task.icon = icon; - task.titleDescription = contentDescription; + task.icon = entry.icon; + task.titleDescription = entry.contentDescription; callback.accept(task); onEnd(); }); @@ -116,51 +112,99 @@ public class TaskIconCache { public void clear() { mIconCache.evictAll(); - mContentDescriptionCache.evictAll(); } - /** - * Loads the content description for the given {@param task}. - */ - private String loadContentDescriptionInBackground(Task task) { - // Return the cached content description if it exists - String label = mContentDescriptionCache.getAndInvalidateIfModified(task.key); - if (label != null) { - return label; - } - - // Skip loading content descriptions if accessibility is disabled unless low RAM recents - // is enabled. - if (!GO_LOW_RAM_RECENTS_ENABLED && !mAccessibilityManager.isEnabled()) { - return ""; - } - - // Skip loading the content description if the activity no longer exists - ActivityInfo activityInfo = mIconLoader.getAndUpdateActivityInfo(task.key); - if (activityInfo == null) { - return ""; - } - - // Load the label otherwise - label = ActivityManagerWrapper.getInstance().getBadgedContentDescription(activityInfo, - task.key.userId, task.taskDescription); - mContentDescriptionCache.put(task.key, label); - return label; - } - - void onTaskRemoved(TaskKey taskKey) { mIconCache.remove(taskKey); } - void invalidatePackage(String packageName) { - // TODO(b/138944598): Merge this class into IconCache so we can do this at the base level - Map activityInfoCache = mActivityInfoCache.snapshot(); - for (ComponentName cn : activityInfoCache.keySet()) { - if (cn.getPackageName().equals(packageName)) { - mActivityInfoCache.remove(cn); + void invalidateCacheEntries(String pkg, UserHandle handle) { + Utilities.postAsyncCallback(mBackgroundHandler, + () -> mIconCache.removeAll(key -> + pkg.equals(key.getPackageName()) && handle.getIdentifier() == key.userId)); + } + + @WorkerThread + private TaskCacheEntry getCacheEntry(Task task) { + TaskCacheEntry entry = mIconCache.getAndInvalidateIfModified(task.key); + if (entry != null) { + return entry; + } + + TaskDescription desc = task.taskDescription; + TaskKey key = task.key; + ActivityInfo activityInfo = null; + + // Create new cache entry + entry = new TaskCacheEntry(); + + // Load icon + // TODO: Load icon resource (b/143363444) + Bitmap icon = desc.getIcon(); + if (icon != null) { + entry.icon = new FastBitmapDrawable(getBitmapInfo( + new BitmapDrawable(mContext.getResources(), icon), + key.userId, + desc.getPrimaryColor(), + false /* isInstantApp */)); + } else { + activityInfo = PackageManagerWrapper.getInstance().getActivityInfo( + key.getComponent(), key.userId); + if (activityInfo != null) { + BitmapInfo bitmapInfo = getBitmapInfo( + mIconProvider.getIcon(activityInfo, UserHandle.of(key.userId)), + key.userId, + desc.getPrimaryColor(), + activityInfo.applicationInfo.isInstantApp()); + entry.icon = newIcon(mContext, bitmapInfo); + } else { + entry.icon = getDefaultIcon(key.userId); } } + + // Loading content descriptions if accessibility or low RAM recents is enabled. + if (GO_LOW_RAM_RECENTS_ENABLED || mAccessibilityManager.isEnabled()) { + // Skip loading the content description if the activity no longer exists + if (activityInfo == null) { + activityInfo = PackageManagerWrapper.getInstance().getActivityInfo( + key.getComponent(), key.userId); + } + if (activityInfo != null) { + entry.contentDescription = ActivityManagerWrapper.getInstance() + .getBadgedContentDescription(activityInfo, task.key.userId, + task.taskDescription); + } + } + + mIconCache.put(task.key, entry); + return entry; + } + + @WorkerThread + private Drawable getDefaultIcon(int userId) { + synchronized (mDefaultIcons) { + BitmapInfo info = mDefaultIcons.get(userId); + if (info == null) { + try (LauncherIcons la = LauncherIcons.obtain(mContext)) { + info = la.makeDefaultIcon(UserHandle.of(userId)); + } + mDefaultIcons.put(userId, info); + } + return new FastBitmapDrawable(info); + } + } + + @WorkerThread + private BitmapInfo getBitmapInfo(Drawable drawable, int userId, + int primaryColor, boolean isInstantApp) { + try (LauncherIcons la = LauncherIcons.obtain(mContext)) { + la.disableColorExtraction(); + la.setWrapperBackgroundColor(primaryColor); + + // User version code O, so that the icon is always wrapped in an adaptive icon container + return la.createBadgedIconBitmap(drawable, UserHandle.of(userId), + Build.VERSION_CODES.O, isInstantApp); + } } public static abstract class IconLoadRequest extends HandlerRunnable { @@ -168,4 +212,9 @@ public class TaskIconCache { super(handler, null); } } + + private static class TaskCacheEntry { + public Drawable icon; + public String contentDescription = ""; + } } diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java index 3b50c2623b..e47df6c673 100644 --- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java +++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java @@ -27,9 +27,9 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.icons.cache.HandlerRunnable; import com.android.launcher3.util.Preconditions; +import com.android.quickstep.util.TaskKeyLruCache; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task.TaskKey; -import com.android.systemui.shared.recents.model.TaskKeyLruCache; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -41,7 +41,7 @@ public class TaskThumbnailCache { private final Handler mBackgroundHandler; private final int mCacheSize; - private final ThumbnailCache mCache; + private final TaskKeyLruCache mCache; private final HighResLoadingState mHighResLoadingState; public static class HighResLoadingState { @@ -100,7 +100,7 @@ public class TaskThumbnailCache { Resources res = context.getResources(); mCacheSize = res.getInteger(R.integer.recentsThumbnailCacheSize); - mCache = new ThumbnailCache(mCacheSize); + mCache = new TaskKeyLruCache<>(mCacheSize); } /** @@ -223,21 +223,4 @@ public class TaskThumbnailCache { this.reducedResolution = reducedResolution; } } - - private static class ThumbnailCache extends TaskKeyLruCache { - - public ThumbnailCache(int cacheSize) { - super(cacheSize); - } - - /** - * Updates the cache entry if it is already present in the cache - */ - public void updateIfAlreadyInCache(int taskId, ThumbnailData thumbnailData) { - ThumbnailData oldData = getCacheEntry(taskId); - if (oldData != null) { - putCacheEntry(taskId, thumbnailData); - } - } - } } diff --git a/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java b/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java index 4a11601bbb..9ca7f234e7 100644 --- a/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java +++ b/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java @@ -20,10 +20,10 @@ import android.util.Log; import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent; import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CANCEL_TARGET; -import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE; import static com.android.systemui.shared.system.LauncherEventUtil.DISMISS; import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_QUICK_SCRUB_ONBOARDING_TIP; import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_SWIPE_UP_ONBOARDING_TIP; +import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.userevent.nano.LauncherLogProto; diff --git a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java index fe37d609b4..b1c72ce729 100644 --- a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java +++ b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java @@ -31,6 +31,11 @@ public class ActivityInitListener implements SchedulerCa private final BiPredicate mOnInitListener; private final ActivityTracker mActivityTracker; + /** + * @param onInitListener a callback made when the activity is initialized. The callback should + * return true to continue receiving callbacks (ie. for if the activity is + * recreated). + */ public ActivityInitListener(BiPredicate onInitListener, ActivityTracker tracker) { mOnInitListener = onInitListener; @@ -42,6 +47,10 @@ public class ActivityInitListener implements SchedulerCa return mOnInitListener.test(activity, alreadyOnHome); } + /** + * Registers the activity-created listener. If the activity is already created, then the + * callback provided in the constructor will be called synchronously. + */ public void register() { mActivityTracker.schedule(this); } diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java index 050bdff09b..2e118b44df 100644 --- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java +++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java @@ -26,7 +26,7 @@ import androidx.annotation.IntDef; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; -import com.android.launcher3.config.FeatureFlags; +import com.android.quickstep.SysUINavigationMode; import java.lang.annotation.Retention; @@ -39,12 +39,27 @@ public class LayoutUtils { @IntDef({MULTI_WINDOW_STRATEGY_HALF_SCREEN, MULTI_WINDOW_STRATEGY_DEVICE_PROFILE}) private @interface MultiWindowStrategy {} + /** + * The height for the swipe up motion + */ + public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) { + float swipeHeight = dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx; + if (SysUINavigationMode.getMode(context) == SysUINavigationMode.Mode.NO_BUTTON) { + swipeHeight -= dp.getInsets().bottom; + } + return swipeHeight; + } + public static void calculateLauncherTaskSize(Context context, DeviceProfile dp, Rect outRect) { float extraSpace; if (dp.isVerticalBarLayout()) { extraSpace = 0; } else { - extraSpace = dp.hotseatBarSizePx + dp.verticalDragHandleSizePx; + Resources res = context.getResources(); + + extraSpace = getDefaultSwipeHeight(context, dp) + dp.verticalDragHandleSizePx + + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size) + + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding); } calculateTaskSize(context, dp, extraSpace, MULTI_WINDOW_STRATEGY_HALF_SCREEN, outRect); } diff --git a/quickstep/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/src/com/android/quickstep/util/NavBarPosition.java new file mode 100644 index 0000000000..a4614dec7f --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/NavBarPosition.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.util; + +import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; + +import android.content.Context; +import android.graphics.Rect; +import android.view.Gravity; +import android.view.Surface; + +import com.android.launcher3.graphics.RotationMode; +import com.android.launcher3.util.DefaultDisplay; +import com.android.quickstep.SysUINavigationMode; + +/** + * Utility class to check nav bar position. + */ +public class NavBarPosition { + + public static RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) { + @Override + public void mapRect(int left, int top, int right, int bottom, Rect out) { + out.left = top; + out.top = right; + out.right = bottom; + out.bottom = left; + } + + @Override + public void mapInsets(Context context, Rect insets, Rect out) { + // If there is a display cutout, the top insets in portrait would also include the + // cutout, which we will get as the left inset in landscape. Using the max of left and + // top allows us to cover both cases (with or without cutout). + if (SysUINavigationMode.getMode(context) == NO_BUTTON) { + out.top = Math.max(insets.top, insets.left); + out.bottom = Math.max(insets.right, insets.bottom); + out.left = out.right = 0; + } else { + out.top = Math.max(insets.top, insets.left); + out.bottom = insets.right; + out.left = insets.bottom; + out.right = 0; + } + } + }; + + public static RotationMode ROTATION_SEASCAPE = new RotationMode(90) { + @Override + public void mapRect(int left, int top, int right, int bottom, Rect out) { + out.left = bottom; + out.top = left; + out.right = top; + out.bottom = right; + } + + @Override + public void mapInsets(Context context, Rect insets, Rect out) { + if (SysUINavigationMode.getMode(context) == NO_BUTTON) { + out.top = Math.max(insets.top, insets.right); + out.bottom = Math.max(insets.left, insets.bottom); + out.left = out.right = 0; + } else { + out.top = Math.max(insets.top, insets.right); + out.bottom = insets.left; + out.right = insets.bottom; + out.left = 0; + } + } + + @Override + public int toNaturalGravity(int absoluteGravity) { + int horizontalGravity = absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK; + int verticalGravity = absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK; + + if (horizontalGravity == Gravity.RIGHT) { + horizontalGravity = Gravity.LEFT; + } else if (horizontalGravity == Gravity.LEFT) { + horizontalGravity = Gravity.RIGHT; + } + + if (verticalGravity == Gravity.TOP) { + verticalGravity = Gravity.BOTTOM; + } else if (verticalGravity == Gravity.BOTTOM) { + verticalGravity = Gravity.TOP; + } + + return ((absoluteGravity & ~Gravity.HORIZONTAL_GRAVITY_MASK) + & ~Gravity.VERTICAL_GRAVITY_MASK) + | horizontalGravity | verticalGravity; + } + }; + + private final SysUINavigationMode.Mode mMode; + private final int mDisplayRotation; + + public NavBarPosition(SysUINavigationMode.Mode mode, DefaultDisplay.Info info) { + mMode = mode; + mDisplayRotation = info.rotation; + } + + public boolean isRightEdge() { + return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90; + } + + public boolean isLeftEdge() { + return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_270; + } + + public RotationMode getRotationMode() { + return isLeftEdge() ? ROTATION_SEASCAPE + : (isRightEdge() ? ROTATION_LANDSCAPE : RotationMode.NORMAL); + } +} diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java new file mode 100644 index 0000000000..d87feec069 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.util; + +import android.util.Log; + +import com.android.systemui.shared.recents.model.Task.TaskKey; + +import java.util.LinkedHashMap; +import java.util.function.Predicate; + +/** + * A simple LRU cache for task key entries + * @param The type of the value + */ +public class TaskKeyLruCache { + + private final MyLinkedHashMap mMap; + + public TaskKeyLruCache(int maxSize) { + mMap = new MyLinkedHashMap<>(maxSize); + } + + /** + * Removes all entries from the cache + */ + public synchronized void evictAll() { + mMap.clear(); + } + + /** + * Removes a particular entry from the cache + */ + public synchronized void remove(TaskKey key) { + mMap.remove(key.id); + } + + /** + * Removes all entries matching keyCheck + */ + public synchronized void removeAll(Predicate keyCheck) { + mMap.entrySet().removeIf(e -> keyCheck.test(e.getValue().mKey)); + } + + /** + * Gets the entry if it is still valid + */ + public synchronized V getAndInvalidateIfModified(TaskKey key) { + Entry entry = mMap.get(key.id); + + if (entry != null && entry.mKey.windowingMode == key.windowingMode + && entry.mKey.lastActiveTime == key.lastActiveTime) { + return entry.mValue; + } else { + remove(key); + return null; + } + } + + /** + * Adds an entry to the cache, optionally evicting the last accessed entry + */ + public final synchronized void put(TaskKey key, V value) { + if (key != null && value != null) { + mMap.put(key.id, new Entry<>(key, value)); + } else { + Log.e("TaskKeyCache", "Unexpected null key or value: " + key + ", " + value); + } + } + + /** + * Updates the cache entry if it is already present in the cache + */ + public synchronized void updateIfAlreadyInCache(int taskId, V data) { + Entry entry = mMap.get(taskId); + if (entry != null) { + entry.mValue = data; + } + } + + private static class Entry { + + final TaskKey mKey; + V mValue; + + Entry(TaskKey key, V value) { + mKey = key; + mValue = value; + } + + @Override + public int hashCode() { + return mKey.id; + } + } + + private static class MyLinkedHashMap extends LinkedHashMap> { + + private final int mMaxSize; + + MyLinkedHashMap(int maxSize) { + super(0, 0.75f, true /* accessOrder */); + mMaxSize = maxSize; + } + + @Override + protected boolean removeEldestEntry(Entry> eldest) { + return size() > mMaxSize; + } + } +} diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java index 26e9eaf01e..0e591cac7a 100644 --- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java +++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java @@ -38,12 +38,12 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.Interpolators; -import com.android.launcher3.uioverrides.states.OverviewState; import com.android.launcher3.util.Themes; import com.android.launcher3.views.ScrimView; import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; +import com.android.quickstep.util.LayoutUtils; /** * Scrim used for all-apps and shelf in Overview @@ -163,7 +163,7 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis int hotseatSize = dp.hotseatBarSizePx + dp.getInsets().bottom + hotseatPadding.bottom + hotseatPadding.top; float dragHandleTop = - Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(context, dp)); + Math.min(hotseatSize, LayoutUtils.getDefaultSwipeHeight(context, dp)); mDragHandleProgress = 1 - (dragHandleTop / mShiftRange); } mTopOffset = dp.getInsets().top - mShelfOffset; diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java index aa5fce19d9..85cffaa107 100644 --- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java +++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java @@ -60,6 +60,7 @@ import com.android.launcher3.util.rule.FailureWatcher; import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch; import com.android.quickstep.views.RecentsView; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; @@ -99,7 +100,7 @@ public class FallbackRecentsTest { } mOrderSensitiveRules = RuleChain.outerRule(new NavigationModeSwitchRule(mLauncher)) - .around(new FailureWatcher(mDevice)); + .around(new FailureWatcher(mDevice)); mOtherLauncherActivity = context.getPackageManager().queryIntentActivities( getHomeIntentInPackage(context), @@ -129,6 +130,7 @@ public class FallbackRecentsTest { @NavigationModeSwitch @Test + @Ignore // b/143488140 public void goToOverviewFromHome() { mDevice.pressHome(); assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg( @@ -139,6 +141,7 @@ public class FallbackRecentsTest { @NavigationModeSwitch @Test + @Ignore // b/143488140 public void goToOverviewFromApp() { startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); @@ -162,7 +165,7 @@ public class FallbackRecentsTest { } result[0] = f.apply(activity); return true; - }).get(), DEFAULT_UI_TIMEOUT); + }).get(), DEFAULT_UI_TIMEOUT, mLauncher); return (T) result[0]; } @@ -173,12 +176,13 @@ public class FallbackRecentsTest { @NavigationModeSwitch @Test + @Ignore // b/143488140 public void testOverview() { startAppFastAndWaitForRecentTask(getAppPackageName()); startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); startTestActivity(2); Wait.atMost("Expected three apps in the task list", - () -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT); + () -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher); BaseOverview overview = mLauncher.getBackground().switchToOverview(); executeOnRecents(recents -> @@ -237,7 +241,8 @@ public class FallbackRecentsTest { private void startAppFastAndWaitForRecentTask(String packageName) { startAppFast(packageName); Wait.atMost("Expected app in task list", - () -> containsRecentTaskWithPackage(packageName), DEFAULT_ACTIVITY_TIMEOUT); + () -> containsRecentTaskWithPackage(packageName), DEFAULT_ACTIVITY_TIMEOUT, + mLauncher); } private boolean containsRecentTaskWithPackage(String packageName) { diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java index c2197ab709..fa4c7b9fa1 100644 --- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java +++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java @@ -30,6 +30,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.util.Log; +import androidx.test.uiautomator.By; import androidx.test.uiautomator.UiDevice; import com.android.launcher3.tapl.LauncherInstrumentation; @@ -80,7 +81,13 @@ public class NavigationModeSwitchRule implements TestRule { Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode(); return new Statement() { private void assertTrue(String message, boolean condition) { - if(!condition) { + if (mLauncher.getDevice().hasObject(By.textStartsWith(""))) { + // The condition above is "screen is not empty". We are not treating + // "Screen is empty" as an anomaly here. It's an acceptable state when + // Launcher just starts under instrumentation. + mLauncher.checkForAnomaly(); + } + if (!condition) { final AssertionError assertionError = new AssertionError(message); FailureWatcher.onError(mLauncher.getDevice(), description, assertionError); throw assertionError; diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java index f5b9b7e880..d60fa1e3c3 100644 --- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java +++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java @@ -18,6 +18,9 @@ package com.android.quickstep; import static com.android.launcher3.util.RaceConditionReproducer.enterEvt; import static com.android.launcher3.util.RaceConditionReproducer.exitEvt; +import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_PRESUBMIT; +import static com.android.launcher3.util.rule.TestStabilityRule.RUN_FLAFOR; +import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_PRESUBMIT; import android.content.Intent; @@ -25,6 +28,7 @@ import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; import com.android.launcher3.Launcher; +import com.android.launcher3.tapl.TestHelpers; import com.android.launcher3.util.RaceConditionReproducer; import com.android.quickstep.NavigationModeSwitchRule.Mode; import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch; diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java index d270d762d9..428e6475bd 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java @@ -248,33 +248,33 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { @Test @NavigationModeSwitch @PortraitLandscape - @Ignore("Temporarily disabled b/140252765") + @Ignore // b/143285809 public void testQuickSwitchFromApp() throws Exception { - startAppFast(getAppPackageName()); startTestActivity(2); - String calculatorPackage = resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR); - startAppFast(calculatorPackage); + startTestActivity(3); + startTestActivity(4); Background background = getAndAssertBackground(); background.quickSwitchToPreviousApp(); assertTrue("The first app we should have quick switched to is not running", - isTestActivityRunning("TestActivity2")); + isTestActivityRunning(3)); background = getAndAssertBackground(); background.quickSwitchToPreviousApp(); if (mLauncher.getNavigationModel() == NavigationModel.THREE_BUTTON) { // 3-button mode toggles between 2 apps, rather than going back further. assertTrue("Second quick switch should have returned to the first app.", - mDevice.wait(Until.hasObject(By.pkg(calculatorPackage)), DEFAULT_UI_TIMEOUT)); + isTestActivityRunning(4)); } else { assertTrue("The second app we should have quick switched to is not running", - isTestActivityRunning("Test Pin Item")); + isTestActivityRunning(2)); } getAndAssertBackground(); } - private boolean isTestActivityRunning(String activityLabel) { - return mDevice.wait(Until.hasObject(By.pkg(getAppPackageName()).text(activityLabel)), + private boolean isTestActivityRunning(int activityNumber) { + return mDevice.wait(Until.hasObject(By.pkg(getAppPackageName()) + .text("TestActivity" + activityNumber)), DEFAULT_UI_TIMEOUT); } @@ -285,7 +285,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { startTestActivity(2); mLauncher.pressHome().quickSwitchToPreviousApp(); assertTrue("The most recent task is not running after quick switching from home", - isTestActivityRunning("TestActivity2")); + isTestActivityRunning(2)); getAndAssertBackground(); } } diff --git a/res/anim/slide_in_right.xml b/res/anim/slide_in_right.xml deleted file mode 100644 index 55d3e54025..0000000000 --- a/res/anim/slide_in_right.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 7be584e932..de17eb7b4f 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -115,8 +115,6 @@ - - @@ -132,12 +130,6 @@ - - - - - - diff --git a/res/values/config.xml b/res/values/config.xml index 038718473b..2a1f6f7864 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -1,5 +1,5 @@ - + false false false @@ -21,10 +21,10 @@ com.android.launcher3.settings.SettingsActivity$LauncherSettingsFragment - + - + 90 @@ -34,7 +34,7 @@ - + 900 @@ -57,29 +57,20 @@ 200 - + true - - - - - - - - - @@ -97,7 +88,12 @@ 40 300 - + + + + + + @@ -112,10 +108,10 @@ - + - + diff --git a/res/values/strings.xml b/res/values/strings.xml index 9d9c2e8ded..dec8939faf 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -102,9 +102,11 @@ App info Install - Dismiss prediction + + Pin Prediction + diff --git a/res/values/styles.xml b/res/values/styles.xml index 339aef5b60..80c791c589 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -130,6 +130,10 @@ @style/WidgetContainerTheme + + - + condition, long timeout) { if (!TestHelpers.isInLauncherProcess()) return; - Wait.atMost(message, () -> getFromLauncher(condition), timeout); + Wait.atMost(message, () -> getFromLauncher(condition), timeout, mLauncher); } // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide @@ -317,7 +317,7 @@ public abstract class AbstractLauncherUiTest { final Object fromLauncher = getFromLauncher(f); output[0] = fromLauncher; return fromLauncher != null; - }, timeout); + }, timeout, mLauncher); return (T) output[0]; } @@ -331,7 +331,7 @@ public abstract class AbstractLauncherUiTest { Wait.atMost(message, () -> { testThreadAction.run(); return getFromLauncher(condition); - }, timeout); + }, timeout, mLauncher); } protected LauncherActivityInfo getSettingsApp() { @@ -373,7 +373,8 @@ public abstract class AbstractLauncherUiTest { startIntent( getInstrumentation().getContext().getPackageManager().getLaunchIntentForPackage( packageName), - By.pkg(packageName).depth(0)); + By.pkg(packageName).depth(0), + true /* newTask */); } public static void startTestActivity(int activityNumber) { @@ -382,12 +383,17 @@ public abstract class AbstractLauncherUiTest { getLaunchIntentForPackage(packageName); intent.setComponent(new ComponentName(packageName, "com.android.launcher3.tests.Activity" + activityNumber)); - startIntent(intent, By.pkg(packageName).text("TestActivity" + activityNumber)); + startIntent(intent, By.pkg(packageName).text("TestActivity" + activityNumber), + false /* newTask */); } - private static void startIntent(Intent intent, BySelector selector) { + private static void startIntent(Intent intent, BySelector selector, boolean newTask) { intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + if (newTask) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + } else { + intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT); + } getInstrumentation().getTargetContext().startActivity(intent); assertTrue("App didn't start: " + selector, UiDevice.getInstance(getInstrumentation()) diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java index 5e87612f43..d7096b0c20 100644 --- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java +++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java @@ -339,23 +339,6 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { } } - /** - * Test dragging a custom shortcut to the workspace and launch it. - * - * A custom shortcut is a 1x1 widget that launches a specific intent when user tap on it. - * Custom shortcuts are replaced by deep shortcuts after api 25. - */ - @Test - @Ignore("Temporarily disabled to unblock merging to master") - @PortraitLandscape - public void testDragCustomShortcut() { - mLauncher.getWorkspace().openAllWidgets() - .getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity") - .dragToWorkspace(); - mLauncher.getWorkspace().getWorkspaceAppIcon("Shortcut") - .launch(getAppPackageName()); - } - public static String getAppPackageName() { return getInstrumentation().getContext().getPackageName(); } diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java index e1b3edeb5a..0472ce1c6f 100644 --- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java @@ -103,12 +103,12 @@ public class AddConfigWidgetTest extends AbstractLauncherUiTest { setResult(acceptConfig); if (acceptConfig) { - Wait.atMost(null, new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT); + Wait.atMost(null, new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT, mLauncher); assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId)); } else { // Verify that the widget id is deleted. Wait.atMost(null, () -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null, - DEFAULT_ACTIVITY_TIMEOUT); + DEFAULT_ACTIVITY_TIMEOUT, mLauncher); } } diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java index b8ca5ded38..f9d1d93b3b 100644 --- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java @@ -69,4 +69,22 @@ public class AddWidgetTest extends AbstractLauncherUiTest { assertNotNull("Widget not found on the workspace", widget); widget.launch(getAppPackageName()); } + + /** + * Test dragging a custom shortcut to the workspace and launch it. + * + * A custom shortcut is a 1x1 widget that launches a specific intent when user tap on it. + * Custom shortcuts are replaced by deep shortcuts after api 25. + */ + @Test + @PortraitLandscape + public void testDragCustomShortcut() throws Throwable { + clearHomescreen(); + mDevice.pressHome(); + mLauncher.getWorkspace().openAllWidgets() + .getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity") + .dragToWorkspace(); + mLauncher.getWorkspace().getWorkspaceAppIcon("Shortcut") + .launch(getAppPackageName()); + } } diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java index 07129ddd95..d909ad7158 100644 --- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java +++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java @@ -59,7 +59,8 @@ import java.util.UUID; @RunWith(AndroidJUnit4.class) public class RequestPinItemTest extends AbstractLauncherUiTest { - @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); + @Rule + public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); private String mCallbackAction; private String mShortcutId; @@ -84,10 +85,10 @@ public class RequestPinItemTest extends AbstractLauncherUiTest { .equals(AppWidgetNoConfig.class.getName())); } - @Test + @Test public void testPinWidgetNoConfig_customPreview() throws Throwable { // Command to set custom preview - Intent command = RequestPinItemActivity.getCommandIntent( + Intent command = RequestPinItemActivity.getCommandIntent( RequestPinItemActivity.class, "setRemoteViewColor").putExtra( RequestPinItemActivity.EXTRA_PARAM + "0", Color.RED); @@ -169,7 +170,8 @@ public class RequestPinItemTest extends AbstractLauncherUiTest { // Go back to home mLauncher.pressHome(); - Wait.atMost(null, new ItemSearchCondition(itemMatcher), DEFAULT_ACTIVITY_TIMEOUT); + Wait.atMost(null, new ItemSearchCondition(itemMatcher), DEFAULT_ACTIVITY_TIMEOUT, + mLauncher); } /** diff --git a/tests/src/com/android/launcher3/util/Wait.java b/tests/src/com/android/launcher3/util/Wait.java index 899686bd61..2663d02760 100644 --- a/tests/src/com/android/launcher3/util/Wait.java +++ b/tests/src/com/android/launcher3/util/Wait.java @@ -3,6 +3,8 @@ package com.android.launcher3.util; import android.os.SystemClock; import android.util.Log; +import com.android.launcher3.tapl.LauncherInstrumentation; + import org.junit.Assert; /** @@ -12,11 +14,13 @@ public class Wait { private static final long DEFAULT_SLEEP_MS = 200; - public static void atMost(String message, Condition condition, long timeout) { - atMost(message, condition, timeout, DEFAULT_SLEEP_MS); + public static void atMost(String message, Condition condition, long timeout, + LauncherInstrumentation launcher) { + atMost(message, condition, timeout, DEFAULT_SLEEP_MS, launcher); } - public static void atMost(String message, Condition condition, long timeout, long sleepMillis) { + public static void atMost(String message, Condition condition, long timeout, long sleepMillis, + LauncherInstrumentation launcher) { final long startTime = SystemClock.uptimeMillis(); long endTime = startTime + timeout; Log.d("Wait", "atMost: " + startTime + " - " + endTime); @@ -40,6 +44,7 @@ public class Wait { throw new RuntimeException(t); } Log.d("Wait", "atMost: timed out: " + SystemClock.uptimeMillis()); + launcher.checkForAnomaly(); Assert.fail(message); } } diff --git a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java index 69bf01d40c..858cb384d7 100644 --- a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java +++ b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java @@ -21,6 +21,7 @@ import android.content.pm.PackageManager; import android.os.Build; import android.util.Log; +import androidx.test.InstrumentationRegistry; import androidx.test.uiautomator.UiDevice; import org.junit.rules.TestRule; @@ -57,7 +58,7 @@ public class TestStabilityRule implements TestRule { public static final int PLATFORM_PRESUBMIT = 0x8; public static final int PLATFORM_POSTSUBMIT = 0x10; - private static final int RUN_FLAFOR = getRunFlavor(); + public static final int RUN_FLAFOR = getRunFlavor(); @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @@ -86,6 +87,19 @@ public class TestStabilityRule implements TestRule { } private static int getRunFlavor() { + final String flavorOverride = InstrumentationRegistry.getArguments().getString("flavor"); + + if (flavorOverride != null) { + Log.d(TAG, "Flavor override: " + flavorOverride); + try { + return (int) TestStabilityRule.class.getField(flavorOverride).get(null); + } catch (NoSuchFieldException e) { + throw new AssertionError("Unrecognized run flavor override: " + flavorOverride); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + final String launcherVersion; try { launcherVersion = getInstrumentation(). diff --git a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java index a31d8a6561..c7f7cd6ad6 100644 --- a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java +++ b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java @@ -23,16 +23,19 @@ import static org.mockito.Mockito.verify; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.graphics.Bitmap; +import android.view.LayoutInflater; + +import androidx.recyclerview.widget.RecyclerView; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import android.view.LayoutInflater; -import com.android.launcher3.icons.IconCache; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.compat.AppWidgetManagerCompat; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.util.MultiHashMap; @@ -46,8 +49,6 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Map; -import androidx.recyclerview.widget.RecyclerView; - @SmallTest @RunWith(AndroidJUnit4.class) public class WidgetsListAdapterTest { @@ -136,7 +137,7 @@ public class WidgetsListAdapterTest { PackageItemInfo pInfo = new PackageItemInfo(wi.componentName.getPackageName()); pInfo.title = pInfo.packageName; pInfo.user = wi.user; - pInfo.iconBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8); + pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0); newMap.addToList(pInfo, wi); if (newMap.size() == num) { break; diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java index e1e9b8d851..3bdeb14925 100644 --- a/tests/tapl/com/android/launcher3/tapl/AllApps.java +++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java @@ -29,6 +29,8 @@ import androidx.test.uiautomator.UiObject2; import com.android.launcher3.ResourceUtils; import com.android.launcher3.testing.TestProtocol; +import java.util.stream.Collectors; + /** * Operations on AllApps opened from Home. Also a parent for All Apps opened from Overview. */ @@ -36,6 +38,7 @@ public class AllApps extends LauncherInstrumentation.VisibleContainer { private static final int MAX_SCROLL_ATTEMPTS = 40; private final int mHeight; + private final int mIconHeight; AllApps(LauncherInstrumentation launcher) { super(launcher); @@ -46,6 +49,9 @@ public class AllApps extends LauncherInstrumentation.VisibleContainer { // Wait for the recycler to populate. mLauncher.waitForObjectInContainer(appListRecycler, By.clazz(TextView.class)); verifyNotFrozen("All apps freeze flags upon opening all apps"); + mIconHeight = mLauncher.getTestInfo( + TestProtocol.REQUEST_ICON_HEIGHT). + getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); } @Override @@ -62,12 +68,16 @@ public class AllApps extends LauncherInstrumentation.VisibleContainer { } final Rect iconBounds = icon.getVisibleBounds(); LauncherInstrumentation.log("hasClickableIcon: icon bounds: " + iconBounds); + if (iconBounds.height() < mIconHeight / 2) { + LauncherInstrumentation.log("hasClickableIcon: icon has insufficient height"); + return false; + } if (iconCenterInSearchBox(allAppsContainer, icon)) { LauncherInstrumentation.log("hasClickableIcon: icon center is under search box"); return false; } if (iconBounds.bottom > displayBottom) { - LauncherInstrumentation.log("hasClickableIcon: icon center bellow bottom offset"); + LauncherInstrumentation.log("hasClickableIcon: icon bottom below bottom offset"); return false; } LauncherInstrumentation.log("hasClickableIcon: icon is clickable"); @@ -100,11 +110,6 @@ public class AllApps extends LauncherInstrumentation.VisibleContainer { ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1; int deviceHeight = mLauncher.getDevice().getDisplayHeight(); int displayBottom = deviceHeight - bottomGestureMargin; - allAppsContainer.setGestureMargins( - 0, - getSearchBox(allAppsContainer).getVisibleBounds().bottom + 1, - 0, - bottomGestureMargin); final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher); if (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector, displayBottom)) { @@ -116,7 +121,12 @@ public class AllApps extends LauncherInstrumentation.VisibleContainer { displayBottom)) { mLauncher.scrollToLastVisibleRow( allAppsContainer, - mLauncher.getObjectsInContainer(allAppsContainer, "icon"), + mLauncher.getObjectsInContainer(allAppsContainer, "icon") + .stream() + .filter(icon -> + icon.getVisibleBounds().bottom + <= displayBottom) + .collect(Collectors.toList()), searchBox.getVisibleBounds().bottom - allAppsContainer.getVisibleBounds().top); final int newScroll = getAllAppsScroll(); @@ -163,7 +173,8 @@ public class AllApps extends LauncherInstrumentation.VisibleContainer { "Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS, ++attempts <= MAX_SCROLL_ATTEMPTS); - mLauncher.scroll(allAppsContainer, Direction.UP, margins, 50); + mLauncher.scroll( + allAppsContainer, Direction.UP, margins, 12, false); } try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled up")) { @@ -191,7 +202,7 @@ public class AllApps extends LauncherInstrumentation.VisibleContainer { final UiObject2 allAppsContainer = verifyActiveContainer(); // Start the gesture in the center to avoid starting at elements near the top. mLauncher.scroll( - allAppsContainer, Direction.DOWN, new Rect(0, 0, 0, mHeight / 2), 10); + allAppsContainer, Direction.DOWN, new Rect(0, 0, 0, mHeight / 2), 10, false); verifyActiveContainer(); } } @@ -205,7 +216,7 @@ public class AllApps extends LauncherInstrumentation.VisibleContainer { final UiObject2 allAppsContainer = verifyActiveContainer(); // Start the gesture in the center, for symmetry with forward. mLauncher.scroll( - allAppsContainer, Direction.UP, new Rect(0, mHeight / 2, 0, 0), 10); + allAppsContainer, Direction.UP, new Rect(0, mHeight / 2, 0, 0), 10, false); verifyActiveContainer(); } } diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java index 0d9038f57b..6583d32189 100644 --- a/tests/tapl/com/android/launcher3/tapl/Background.java +++ b/tests/tapl/com/android/launcher3/tapl/Background.java @@ -106,7 +106,7 @@ public class Background extends LauncherInstrumentation.VisibleContainer { } if (mLauncher.isFallbackOverview()) { - mLauncher.linearGesture(startX, startY, endX, endY, 10); + mLauncher.linearGesture(startX, startY, endX, endY, 10, false); new BaseOverview(mLauncher); } else { mLauncher.swipeToState(startX, startY, endX, endY, 10, expectedState); diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java index 8ccfc05bf7..339e14feca 100644 --- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java +++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java @@ -55,7 +55,8 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { final int leftMargin = mLauncher.getTestInfo( TestProtocol.REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN). getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); - mLauncher.scroll(overview, Direction.LEFT, new Rect(leftMargin + 1, 0, 0, 0), 20); + mLauncher.scroll( + overview, Direction.LEFT, new Rect(leftMargin + 1, 0, 0, 0), 20, false); verifyActiveContainer(); } } @@ -89,7 +90,8 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { final int rightMargin = mLauncher.getTestInfo( TestProtocol.REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN). getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); - mLauncher.scroll(overview, Direction.RIGHT, new Rect(0, 0, rightMargin + 1, 0), 20); + mLauncher.scroll( + overview, Direction.RIGHT, new Rect(0, 0, rightMargin + 1, 0), 20, false); verifyActiveContainer(); } } diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 0879404ca1..d26fac94a6 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -64,10 +64,12 @@ import com.android.launcher3.ResourceUtils; import com.android.launcher3.testing.TestProtocol; import com.android.systemui.shared.system.QuickStepContract; -import java.util.ArrayList; +import org.junit.Assert; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Deque; @@ -77,8 +79,6 @@ import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import java.util.function.Function; -import org.junit.Assert; - /** * The main tapl object. The only object that can be explicitly constructed by the using code. It * produces all other objects. @@ -298,6 +298,14 @@ public final class LauncherInstrumentation { return null; } + public void checkForAnomaly() { + final String anomalyMessage = getAnomalyMessage(); + if (anomalyMessage != null) { + failWithSystemHealth( + "Tests are broken by a non-Launcher system error: " + anomalyMessage); + } + } + private String getVisibleStateMessage() { if (hasLauncherObject(WIDGETS_RES_ID)) return "Widgets"; if (hasLauncherObject(OVERVIEW_RES_ID)) return "Overview"; @@ -331,20 +339,17 @@ public final class LauncherInstrumentation { } private void fail(String message) { - message = "http://go/tapl : " + getContextDescription() + message; + checkForAnomaly(); - final String anomaly = getAnomalyMessage(); - if (anomaly != null) { - message = anomaly + ", which causes:\n" + message; - } else { - message = message + " (visible state: " + getVisibleStateMessage() + ")"; - } + failWithSystemHealth("http://go/tapl : " + getContextDescription() + message + + " (visible state: " + getVisibleStateMessage() + ")"); + } + private void failWithSystemHealth(String message) { final String systemHealth = getSystemHealthMessage(); if (systemHealth != null) { message = message - + ", which might be a consequence of system health " - + "problems:\n<<<<<<<<<<<<<<<<<<\n" + + ", perhaps because of system health problems:\n<<<<<<<<<<<<<<<<<<\n" + systemHealth + "\n>>>>>>>>>>>>>>>>>>"; } @@ -424,11 +429,7 @@ public final class LauncherInstrumentation { // b/136278866 for (int i = 0; i != 100; ++i) { if (getNavigationModeMismatchError() == null) break; - try { - Thread.sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); - } + sleep(100); } final String error = getNavigationModeMismatchError(); @@ -497,7 +498,7 @@ public final class LauncherInstrumentation { } public void waitForLauncherInitialized() { - for (int i = 0; i < 600; ++i) { + for (int i = 0; i < 100; ++i) { if (getTestInfo( TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED). getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD)) { @@ -535,8 +536,7 @@ public final class LauncherInstrumentation { // accessibility events prior to pressing Home. final String action; if (getNavigationModel() == NavigationModel.ZERO_BUTTON) { - final String anomaly = getAnomalyMessage(); - if (anomaly != null) fail("Can't swipe up to Home: " + anomaly); + checkForAnomaly(); final Point displaySize = getRealDisplaySize(); @@ -544,7 +544,8 @@ public final class LauncherInstrumentation { linearGesture( displaySize.x / 2, displaySize.y - 1, displaySize.x / 2, 0, - ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME); + ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, + false); try (LauncherInstrumentation.Closable c = addContextLayer( "Swiped up from context menu to home")) { waitUntilGone("deep_shortcuts_container"); @@ -696,8 +697,8 @@ public final class LauncherInstrumentation { final UiObject2 object = container.wait( Until.findObject(getLauncherObjectSelector(resName)), WAIT_TIME_MS); - assertNotNull("Can't find a launcher object id: " + resName + " in container: " + - container.getResourceName(), object); + assertNotNull("Can't find a view in Launcher, id: " + resName + " in container: " + + container.getResourceName(), object); return object; } @@ -706,8 +707,8 @@ public final class LauncherInstrumentation { final UiObject2 object = container.wait( Until.findObject(selector), WAIT_TIME_MS); - assertNotNull("Can't find a launcher object id: " + selector + " in container: " + - container.getResourceName(), object); + assertNotNull("Can't find a view in Launcher, id: " + selector + " in container: " + + container.getResourceName(), object); return object; } @@ -738,7 +739,7 @@ public final class LauncherInstrumentation { private UiObject2 waitForObjectBySelector(BySelector selector) { final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS); - assertNotNull("Can't find a launcher object; selector: " + selector, object); + assertNotNull("Can't find a view in Launcher, selector: " + selector, object); return object; } @@ -769,7 +770,7 @@ public final class LauncherInstrumentation { void swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState) { final Bundle parcel = (Bundle) executeAndWaitForEvent( - () -> linearGesture(startX, startY, endX, endY, steps), + () -> linearGesture(startX, startY, endX, endY, steps, false), event -> TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName()), "Swipe failed to receive an event for the swipe end"); assertEquals("Swipe switched launcher to a wrong state;", @@ -782,31 +783,38 @@ public final class LauncherInstrumentation { ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, getResources()) + 1; } - int getBottomGestureMargin(UiObject2 container) { - return container.getVisibleBounds().bottom - getRealDisplaySize().y - + getBottomGestureSize(); + int getBottomGestureMarginInContainer(UiObject2 container) { + final int bottomGestureStartOnScreen = getRealDisplaySize().y - getBottomGestureSize(); + return container.getVisibleBounds().bottom - bottomGestureStartOnScreen; } - void scrollToLastVisibleRow(UiObject2 container, Collection items, int topPadding) { + void scrollToLastVisibleRow( + UiObject2 container, + Collection items, + int topPaddingInContainer) { final UiObject2 lowestItem = Collections.max(items, (i1, i2) -> Integer.compare(i1.getVisibleBounds().top, i2.getVisibleBounds().top)); - final int gestureStart = lowestItem.getVisibleBounds().top + getTouchSlop(); - final int distance = gestureStart - container.getVisibleBounds().top - topPadding; - final int bottomMargin = container.getVisibleBounds().height() - distance; + final int itemRowCurrentTopOnScreen = lowestItem.getVisibleBounds().top; + final Rect containerRect = container.getVisibleBounds(); + final int itemRowNewTopOnScreen = containerRect.top + topPaddingInContainer; + final int distance = itemRowCurrentTopOnScreen - itemRowNewTopOnScreen + getTouchSlop(); + final int bottomGestureMarginInContainer = getBottomGestureMarginInContainer(container); scroll( container, Direction.DOWN, new Rect( 0, + containerRect.height() - distance - bottomGestureMarginInContainer, 0, - 0, - Math.max(bottomMargin, getBottomGestureMargin(container))), - 150); + bottomGestureMarginInContainer), + 10, + true); } - void scroll(UiObject2 container, Direction direction, Rect margins, int steps) { + void scroll( + UiObject2 container, Direction direction, Rect margins, int steps, boolean slowDown) { final Rect rect = container.getVisibleBounds(); if (margins != null) { rect.left += margins.left; @@ -851,7 +859,7 @@ public final class LauncherInstrumentation { } executeAndWaitForEvent( - () -> linearGesture(startX, startY, endX, endY, steps), + () -> linearGesture(startX, startY, endX, endY, steps, slowDown), event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()), "Didn't receive a scroll end message: " + startX + ", " + startY + ", " + endX + ", " + endY); @@ -859,13 +867,17 @@ public final class LauncherInstrumentation { // Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a // fixed interval each time. - void linearGesture(int startX, int startY, int endX, int endY, int steps) { + void linearGesture(int startX, int startY, int endX, int endY, int steps, boolean slowDown) { log("linearGesture: " + startX + ", " + startY + " -> " + endX + ", " + endY); final long downTime = SystemClock.uptimeMillis(); final Point start = new Point(startX, startY); final Point end = new Point(endX, endY); sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start); - final long endTime = movePointer(downTime, downTime, steps * GESTURE_STEP_MS, start, end); + long endTime = movePointer(downTime, downTime, steps * GESTURE_STEP_MS, start, end); + if (slowDown) { + endTime = movePointer(downTime, endTime + GESTURE_STEP_MS, 5 * GESTURE_STEP_MS, end, + end); + } sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end); } diff --git a/tests/tapl/com/android/launcher3/tapl/Overview.java b/tests/tapl/com/android/launcher3/tapl/Overview.java index 4f8aeb1123..16a64a7499 100644 --- a/tests/tapl/com/android/launcher3/tapl/Overview.java +++ b/tests/tapl/com/android/launcher3/tapl/Overview.java @@ -58,7 +58,7 @@ public final class Overview extends BaseOverview { getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD), mLauncher.getDevice().getDisplayWidth() / 2, 0, - 50, + 12, ALL_APPS_STATE_ORDINAL); try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java index 91f0fc4c83..2ee424bc52 100644 --- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java +++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java @@ -52,7 +52,7 @@ public final class OverviewTask { final Rect taskBounds = mTask.getVisibleBounds(); final int centerX = taskBounds.centerX(); final int centerY = taskBounds.centerY(); - mLauncher.linearGesture(centerX, centerY, centerX, 0, 10); + mLauncher.linearGesture(centerX, centerY, centerX, 0, 10, false); mLauncher.waitForIdle(); } } diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java index 5fcaa557df..d208c667b7 100644 --- a/tests/tapl/com/android/launcher3/tapl/Widgets.java +++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java @@ -48,8 +48,9 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer { mLauncher.scroll( widgetsContainer, Direction.DOWN, - new Rect(0, 0, 0, mLauncher.getBottomGestureMargin(widgetsContainer) + 1), - FLING_STEPS); + new Rect(0, 0, 0, + mLauncher.getBottomGestureMarginInContainer(widgetsContainer) + 1), + FLING_STEPS, false); try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung forward")) { verifyActiveContainer(); } @@ -69,7 +70,7 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer { widgetsContainer, Direction.UP, new Rect(0, 0, widgetsContainer.getVisibleBounds().width(), 0), - FLING_STEPS); + FLING_STEPS, false); try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung back")) { verifyActiveContainer(); } diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java index d1261e07be..3301dd87d8 100644 --- a/tests/tapl/com/android/launcher3/tapl/Workspace.java +++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java @@ -32,13 +32,14 @@ import androidx.test.uiautomator.By; import androidx.test.uiautomator.Direction; import androidx.test.uiautomator.UiObject2; +import com.android.launcher3.ResourceUtils; import com.android.launcher3.testing.TestProtocol; /** * Operations on the workspace screen. */ public final class Workspace extends Home { - private static final int DRAG_DURACTION = 2000; + private static final int DRAG_DURATION = 500; private static final int FLING_STEPS = 10; private final UiObject2 mHotseat; @@ -59,7 +60,11 @@ public final class Workspace extends Home { verifyActiveContainer(); final UiObject2 hotseat = mHotseat; final Point start = hotseat.getVisibleCenter(); - start.y = hotseat.getVisibleBounds().bottom - 1; + int deviceHeight = mLauncher.getDevice().getDisplayHeight(); + int bottomGestureMargin = ResourceUtils.getNavbarSize( + ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()); + int displayBottom = deviceHeight - bottomGestureMargin; + start.y = displayBottom - 1; final int swipeHeight = mLauncher.getTestInfo( TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT). getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); @@ -72,7 +77,7 @@ public final class Workspace extends Home { start.y, start.x, start.y - swipeHeight - mLauncher.getTouchSlop(), - 60, + 12, ALL_APPS_STATE_ORDINAL); try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( @@ -166,7 +171,7 @@ public final class Workspace extends Home { launcher.waitForLauncherObject(longPressIndicator); LauncherInstrumentation.log("dragIconToWorkspace: indicator"); launcher.movePointer( - downTime, SystemClock.uptimeMillis(), DRAG_DURACTION, launchableCenter, dest); + downTime, SystemClock.uptimeMillis(), DRAG_DURATION, launchableCenter, dest); LauncherInstrumentation.log("dragIconToWorkspace: moved pointer"); launcher.sendPointer( downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest); @@ -182,7 +187,7 @@ public final class Workspace extends Home { final UiObject2 workspace = verifyActiveContainer(); mLauncher.scroll(workspace, Direction.RIGHT, new Rect(0, 0, mLauncher.getEdgeSensitivityWidth() + 1, 0), - FLING_STEPS); + FLING_STEPS, false); verifyActiveContainer(); } @@ -194,7 +199,7 @@ public final class Workspace extends Home { final UiObject2 workspace = verifyActiveContainer(); mLauncher.scroll(workspace, Direction.LEFT, new Rect(mLauncher.getEdgeSensitivityWidth() + 1, 0, 0, 0), - FLING_STEPS); + FLING_STEPS, false); verifyActiveContainer(); }