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/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" >
-
+
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..a3c7c078a4 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -116,7 +116,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 +183,10 @@ public class BaseIconFactory implements AutoCloseable {
bitmap = createIconBitmap(badged, 1f);
}
}
- return BitmapInfo.fromBitmap(bitmap, mDisableColorExtractor ? null : mColorExtractor);
+ int color = extractColor(result);
+ return icon instanceof BitmapInfo.Extender
+ ? ((BitmapInfo.Extender) icon).getExtendedInfo(result, color, this)
+ : BitmapInfo.of(result, color);
}
public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) {
@@ -334,6 +337,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/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
index d0db15728a..bcdbce5e29 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
@@ -171,7 +171,8 @@ public class IconCacheUpdateHandler {
long updateTime = c.getLong(indexLastUpdate);
int version = c.getInt(indexVersion);
T app = componentMap.remove(component);
- if (version == info.versionCode && updateTime == info.lastUpdateTime
+ if (version == info.versionCode
+ && updateTime == cachingLogic.getLastUpdatedTime(app, info)
&& TextUtils.equals(c.getString(systemStateIndex),
mIconCache.getIconSystemState(info.packageName))) {
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/src/com/android/launcher3/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
new file mode 100644
index 0000000000..424333c971
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
@@ -0,0 +1,342 @@
+/*
+ * 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.content.ComponentName;
+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;
+
+ private static final String PREDICTION_CLIENT = "hotseat";
+
+ private boolean mDragStarted = false;
+ 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);
+ createPredictor();
+ }
+
+ @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 (mDragStarted) {
+ 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();
+ }
+ }
+
+ private 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)
+ .build());
+ mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
+ this::setPredictedApps);
+ mAppPredictor.requestPredictionUpdate();
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+ removePredictedApps(true);
+ mDragStarted = true;
+ }
+
+ @Override
+ public void onDragEnd() {
+ if (!mDragStarted) {
+ return;
+ }
+ mDragStarted = false;
+ 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;
+ }
+}
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..8338c2e437 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,14 +18,12 @@ 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 com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
import com.android.launcher3.ItemInfoWithIcon;
@@ -36,7 +34,6 @@ 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.util.ComponentKey;
@@ -239,7 +236,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 +247,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
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..7dc561677a 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.fillGapsWithPrediction(false);
+ }
+ }
+
+ @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..4f50e33b00 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,9 +45,9 @@ 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.util.ActiveGestureLog;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AppWindowAnimationHelper;
@@ -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..24f247b2e0 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,21 +13,20 @@
* 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.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.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
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;
@@ -35,32 +34,25 @@ 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,14 @@ public class FallbackNoButtonInputConsumer extends
}
applyTransformUnchecked();
- setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
+ mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
}
@Override
public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+ super.onRecentsAnimationCanceled(thumbnailData);
mRecentsView.setRecentsAnimationTargets(null, null);
- setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+ 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,11 @@ public class WindowTransformSwipeHandler
@Override
public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
super.onRecentsAnimationCanceled(thumbnailData);
- mRecentsView.setRecentsAnimationTargets(null, null);
+ if (mRecentsView != null) {
+ 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 +627,7 @@ public class WindowTransformSwipeHandler
public void onGestureStarted() {
notifyGestureStartedAsync();
mShiftAtGestureStart = mCurrentShift.value;
- setStateOnUiThread(STATE_GESTURE_STARTED);
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
mGestureStarted = true;
}
@@ -656,7 +650,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 +665,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 +680,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 +702,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 +741,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 +762,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 +789,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 +804,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 +837,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 +850,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 +892,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 +917,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 +950,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 +960,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 +1012,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 +1047,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 +1068,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 +1093,7 @@ public class WindowTransformSwipeHandler
? newRunningTaskView.getTask().key.id
: -1;
mRecentsView.setCurrentTask(newRunningTaskId);
- sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId);
+ mGestureState.setFinishingRecentsAnimationTaskId(newRunningTaskId);
}
}
@@ -1089,9 +1112,7 @@ public class WindowTransformSwipeHandler
endLauncherTransitionController();
mRecentsView.onGestureAnimationEnd();
-
- mActivity.getRootView().setOnApplyWindowInsetsListener(null);
- removeLiveTileOverlay();
+ resetLauncherListenersAndOverlays();
}
private void endLauncherTransitionController() {
@@ -1102,58 +1123,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 +1195,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 +1221,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 +1238,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/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/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 5591e40b8d..52ae1156dc 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,6 +509,7 @@ public class TouchInteractionService extends Service implements
return base;
}
+<<<<<<< HEAD
private InputConsumer newBaseConsumer(GestureState gestureState, boolean useSharedState,
MotionEvent event) {
RunningTaskInfo runningTaskInfo = TraceHelper.whitelistIpcs("getRunningTask.0",
@@ -507,15 +517,20 @@ public class TouchInteractionService extends Service implements
if (!useSharedState) {
sSwipeSharedState.clearAllState(false /* finishAnimation */);
}
+=======
+ private InputConsumer newBaseConsumer(GestureState previousGestureState,
+ GestureState gestureState, MotionEvent event) {
+>>>>>>> ub-launcher3-master
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
+<<<<<<< HEAD
runningTaskInfo = TraceHelper.whitelistIpcs("getRunningTask.assistant",
() -> mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT));
@@ -525,80 +540,90 @@ public class TouchInteractionService extends Service implements
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);
+>>>>>>> ub-launcher3-master
}
- 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 +642,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 +657,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 +668,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 +709,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 +737,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 +769,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/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..eaa23a6b28 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.
@@ -859,8 +875,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 +1688,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 +1723,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 +1741,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 +1854,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 +1876,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..5d9a0092c5 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -33,4 +33,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/DejankBinderTracker.java b/quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java
deleted file mode 100644
index d8aa235823..0000000000
--- a/quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java
+++ /dev/null
@@ -1,159 +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 static android.os.IBinder.FLAG_ONEWAY;
-
-import android.os.Binder;
-import android.os.Build;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.MainThread;
-
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.function.BiConsumer;
-import java.util.function.Supplier;
-
-/**
- * A binder proxy transaction listener for tracking non-whitelisted binder calls.
- */
-public class DejankBinderTracker implements Binder.ProxyTransactListener {
- private static final String TAG = "DejankBinderTracker";
-
- private static final Object sLock = new Object();
- private static final HashSet sWhitelistedFrameworkClasses = new HashSet<>();
- static {
- // Common IPCs that are ok to block the main thread.
- sWhitelistedFrameworkClasses.add("android.view.IWindowSession");
- sWhitelistedFrameworkClasses.add("android.os.IPowerManager");
- }
- private static boolean sTemporarilyIgnoreTracking = false;
-
- // Used by the client to limit binder tracking to specific regions
- private static boolean sTrackingAllowed = false;
-
- private BiConsumer mUnexpectedTransactionCallback;
- private boolean mIsTracking = false;
-
- /**
- * Temporarily ignore blocking binder calls for the duration of this {@link Runnable}.
- */
- @MainThread
- public static void whitelistIpcs(Runnable runnable) {
- sTemporarilyIgnoreTracking = true;
- runnable.run();
- sTemporarilyIgnoreTracking = false;
- }
-
- /**
- * Temporarily ignore blocking binder calls for the duration of this {@link Supplier}.
- */
- @MainThread
- public static T whitelistIpcs(Supplier supplier) {
- sTemporarilyIgnoreTracking = true;
- T value = supplier.get();
- sTemporarilyIgnoreTracking = false;
- return value;
- }
-
- /**
- * Enables binder tracking during a test.
- */
- @MainThread
- public static void allowBinderTrackingInTests() {
- sTrackingAllowed = true;
- }
-
- /**
- * Disables binder tracking during a test.
- */
- @MainThread
- public static void disallowBinderTrackingInTests() {
- sTrackingAllowed = false;
- }
-
- public DejankBinderTracker(BiConsumer unexpectedTransactionCallback) {
- mUnexpectedTransactionCallback = unexpectedTransactionCallback;
- }
-
- @MainThread
- public void startTracking() {
- if (!Build.TYPE.toLowerCase(Locale.ROOT).contains("debug")
- && !Build.TYPE.toLowerCase(Locale.ROOT).equals("eng")) {
- Log.wtf(TAG, "Unexpected use of binder tracker in non-debug build", new Exception());
- return;
- }
- if (mIsTracking) {
- return;
- }
- mIsTracking = true;
- Binder.setProxyTransactListener(this);
- }
-
- @MainThread
- public void stopTracking() {
- if (!mIsTracking) {
- return;
- }
- mIsTracking = false;
- Binder.setProxyTransactListener(null);
- }
-
- // Override the hidden Binder#onTransactStarted method
- public synchronized Object onTransactStarted(IBinder binder, int transactionCode, int flags) {
- if (!mIsTracking
- || !sTrackingAllowed
- || sTemporarilyIgnoreTracking
- || (flags & FLAG_ONEWAY) == FLAG_ONEWAY
- || !isMainThread()) {
- return null;
- }
-
- String descriptor;
- try {
- descriptor = binder.getInterfaceDescriptor();
- if (sWhitelistedFrameworkClasses.contains(descriptor)) {
- return null;
- }
- } catch (RemoteException e) {
- e.printStackTrace();
- descriptor = binder.getClass().getSimpleName();
- }
-
- mUnexpectedTransactionCallback.accept(descriptor, transactionCode);
- return null;
- }
-
- @Override
- public Object onTransactStarted(IBinder binder, int transactionCode) {
- // Do nothing
- return null;
- }
-
- @Override
- public void onTransactEnded(Object session) {
- // Do nothing
- }
-
- public static boolean isMainThread() {
- return Thread.currentThread() == Looper.getMainLooper().getThread();
- }
-}
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/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..ca813434a4 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -31,6 +31,9 @@ import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.rule.ShellCommandRule.disableHeadsUpNotification;
import static com.android.launcher3.util.rule.ShellCommandRule.getLauncherCommand;
+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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -99,7 +102,7 @@ public class FallbackRecentsTest {
}
mOrderSensitiveRules = RuleChain.outerRule(new NavigationModeSwitchRule(mLauncher))
- .around(new FailureWatcher(mDevice));
+ .around(new FailureWatcher(mDevice));
mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
getHomeIntentInPackage(context),
@@ -130,6 +133,11 @@ public class FallbackRecentsTest {
@NavigationModeSwitch
@Test
public void goToOverviewFromHome() {
+ // b/142828227
+ if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess() &&
+ (RUN_FLAFOR & (PLATFORM_PRESUBMIT | UNBUNDLED_PRESUBMIT)) != 0) {
+ return;
+ }
mDevice.pressHome();
assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg(
mOtherLauncherActivity.packageName)), WAIT_TIME_MS));
@@ -140,6 +148,11 @@ public class FallbackRecentsTest {
@NavigationModeSwitch
@Test
public void goToOverviewFromApp() {
+ // b/142828227
+ if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess() &&
+ (RUN_FLAFOR & (PLATFORM_PRESUBMIT | UNBUNDLED_PRESUBMIT)) != 0) {
+ return;
+ }
startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
mLauncher.getBackground().switchToOverview();
@@ -162,7 +175,7 @@ public class FallbackRecentsTest {
}
result[0] = f.apply(activity);
return true;
- }).get(), DEFAULT_UI_TIMEOUT);
+ }).get(), DEFAULT_UI_TIMEOUT, mLauncher);
return (T) result[0];
}
@@ -174,11 +187,16 @@ public class FallbackRecentsTest {
@NavigationModeSwitch
@Test
public void testOverview() {
+ // b/142828227
+ if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess() &&
+ (RUN_FLAFOR & (PLATFORM_PRESUBMIT | UNBUNDLED_PRESUBMIT)) != 0) {
+ return;
+ }
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 +255,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/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
index 32eb2ec5fe..5b6d94d732 100644
--- a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -202,15 +202,14 @@ public class BaseModelUpdateTaskTestCase {
CacheEntry entry = mCache.get(new ComponentKey(componentName, user));
if (entry == null) {
entry = new CacheEntry();
- getDefaultIcon(user).applyTo(entry);
+ entry.bitmap = getDefaultIcon(user);
}
return entry;
}
public void addCache(ComponentName key, String title) {
CacheEntry entry = new CacheEntry();
- entry.icon = newIcon();
- entry.color = Color.RED;
+ entry.bitmap = BitmapInfo.of(newIcon(), Color.RED);
entry.title = title;
mCache.put(new ComponentKey(key, Process.myUserHandle()), entry);
}
diff --git a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index 81b90431a0..69c5b00c27 100644
--- a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -2,13 +2,13 @@ package com.android.launcher3.model;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import com.android.launcher3.AppInfo;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.icons.BitmapInfo;
import org.junit.Before;
import org.junit.Test;
@@ -43,7 +43,7 @@ public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase {
public void testCacheUpdate_update_apps() throws Exception {
// Clear all icons from apps list so that its easy to check what was updated
for (AppInfo info : allAppsList.data) {
- info.iconBitmap = null;
+ info.bitmap = BitmapInfo.LOW_RES_INFO;
}
executeTaskForTest(newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, "app1"));
@@ -56,9 +56,9 @@ public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase {
assertFalse(allAppsList.data.isEmpty());
for (AppInfo info : allAppsList.data) {
if (info.componentName.getPackageName().equals("app1")) {
- assertNotNull(info.iconBitmap);
+ assertFalse(info.bitmap.isNullOrLowRes());
} else {
- assertNull(info.iconBitmap);
+ assertTrue(info.bitmap.isNullOrLowRes());
}
}
}
@@ -85,10 +85,10 @@ public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase {
for (ItemInfo info : bgDataModel.itemsIdMap) {
if (updates.contains(info.id)) {
assertEquals(NEW_LABEL_PREFIX + info.id, info.title);
- assertNotNull(((WorkspaceItemInfo) info).iconBitmap);
+ assertFalse(((WorkspaceItemInfo) info).bitmap.isNullOrLowRes());
} else {
assertNotSame(NEW_LABEL_PREFIX + info.id, info.title);
- assertNull(((WorkspaceItemInfo) info).iconBitmap);
+ assertTrue(((WorkspaceItemInfo) info).bitmap.isNullOrLowRes());
}
}
}
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index b28077f7cd..382bfdf513 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -35,7 +35,7 @@ import com.android.launcher3.logging.StatsLogUtils;
import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate;
-import com.android.launcher3.uioverrides.UiFactory;
+import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.ViewCache;
@@ -81,19 +81,39 @@ public abstract class BaseActivity extends Activity
protected StatsLogManager mStatsLogManager;
protected SystemUiController mSystemUiController;
- private static final int ACTIVITY_STATE_STARTED = 1 << 0;
- private static final int ACTIVITY_STATE_RESUMED = 1 << 1;
+
+ public static final int ACTIVITY_STATE_STARTED = 1 << 0;
+ public static final int ACTIVITY_STATE_RESUMED = 1 << 1;
+
/**
- * State flag indicating if the user is active or the actitvity when to background as a result
+ * State flags indicating that the activity has received one frame after resume, and was
+ * not immediately paused.
+ */
+ public static final int ACTIVITY_STATE_DEFERRED_RESUMED = 1 << 2;
+
+ public static final int ACTIVITY_STATE_WINDOW_FOCUSED = 1 << 3;
+
+ /**
+ * State flag indicating if the user is active or the activity when to background as a result
* of user action.
* @see #isUserActive()
*/
- private static final int ACTIVITY_STATE_USER_ACTIVE = 1 << 2;
+ public static final int ACTIVITY_STATE_USER_ACTIVE = 1 << 4;
+
+ /**
+ * State flag indicating that a state transition is in progress
+ */
+ public static final int ACTIVITY_STATE_TRANSITION_ACTIVE = 1 << 5;
@Retention(SOURCE)
@IntDef(
flag = true,
- value = {ACTIVITY_STATE_STARTED, ACTIVITY_STATE_RESUMED, ACTIVITY_STATE_USER_ACTIVE})
+ value = {ACTIVITY_STATE_STARTED,
+ ACTIVITY_STATE_RESUMED,
+ ACTIVITY_STATE_DEFERRED_RESUMED,
+ ACTIVITY_STATE_WINDOW_FOCUSED,
+ ACTIVITY_STATE_USER_ACTIVE,
+ ACTIVITY_STATE_TRANSITION_ACTIVE})
public @interface ActivityFlags{}
@ActivityFlags
@@ -146,19 +166,19 @@ public abstract class BaseActivity extends Activity
@Override
protected void onStart() {
- mActivityFlags |= ACTIVITY_STATE_STARTED;
+ addActivityFlags(ACTIVITY_STATE_STARTED);
super.onStart();
}
@Override
protected void onResume() {
- mActivityFlags |= ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_USER_ACTIVE;
+ addActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_USER_ACTIVE);
super.onResume();
}
@Override
protected void onUserLeaveHint() {
- mActivityFlags &= ~ACTIVITY_STATE_USER_ACTIVE;
+ removeActivityFlags(ACTIVITY_STATE_USER_ACTIVE);
super.onUserLeaveHint();
}
@@ -172,7 +192,7 @@ public abstract class BaseActivity extends Activity
@Override
protected void onStop() {
- mActivityFlags &= ~ACTIVITY_STATE_STARTED & ~ACTIVITY_STATE_USER_ACTIVE;
+ removeActivityFlags(ACTIVITY_STATE_STARTED | ACTIVITY_STATE_USER_ACTIVE);
mForceInvisible = 0;
super.onStop();
@@ -183,7 +203,7 @@ public abstract class BaseActivity extends Activity
@Override
protected void onPause() {
- mActivityFlags &= ~ACTIVITY_STATE_RESUMED;
+ removeActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_DEFERRED_RESUMED);
super.onPause();
// Reset the overridden sysui flags used for the task-swipe launch animation, we do this
@@ -193,6 +213,17 @@ public abstract class BaseActivity extends Activity
getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0);
}
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (hasFocus) {
+ addActivityFlags(ACTIVITY_STATE_WINDOW_FOCUSED);
+ } else {
+ removeActivityFlags(ACTIVITY_STATE_WINDOW_FOCUSED);
+ }
+
+ }
+
public boolean isStarted() {
return (mActivityFlags & ACTIVITY_STATE_STARTED) != 0;
}
@@ -208,6 +239,22 @@ public abstract class BaseActivity extends Activity
return (mActivityFlags & ACTIVITY_STATE_USER_ACTIVE) != 0;
}
+ public int getActivityFlags() {
+ return mActivityFlags;
+ }
+
+ protected void addActivityFlags(int flags) {
+ mActivityFlags |= flags;
+ onActivityFlagsChanged(flags);
+ }
+
+ protected void removeActivityFlags(int flags) {
+ mActivityFlags &= ~flags;
+ onActivityFlagsChanged(flags);
+ }
+
+ protected void onActivityFlagsChanged(int changeBits) { }
+
public void addOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) {
mDPChangeListeners.add(listener);
}
@@ -233,7 +280,7 @@ public abstract class BaseActivity extends Activity
/**
* Used to set the override visibility state, used only to handle the transition home with the
* recents animation.
- * @see QuickstepAppTransitionManagerImpl#getWallpaperOpenRunner()
+ * @see QuickstepAppTransitionManagerImpl#getWallpaperOpenRunner
*/
public void addForceInvisibleFlag(@InvisibilityFlags int flag) {
mForceInvisible |= flag;
@@ -260,7 +307,7 @@ public abstract class BaseActivity extends Activity
@Override
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
- if (!UiFactory.dumpActivity(this, writer)) {
+ if (!ApiWrapper.dumpActivity(this, writer)) {
super.dump(prefix, fd, writer, args);
}
}
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index d24de8e30c..772eb00243 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -57,7 +57,7 @@ public abstract class BaseDraggingActivity extends BaseActivity
private ActionMode mCurrentActionMode;
protected boolean mIsSafeModeEnabled;
- private OnStartCallback mOnStartCallback;
+ private Runnable mOnStartCallback;
private int mThemeRes = R.style.AppTheme;
@@ -226,7 +226,7 @@ public abstract class BaseDraggingActivity extends BaseActivity
super.onStart();
if (mOnStartCallback != null) {
- mOnStartCallback.onActivityStart(this);
+ mOnStartCallback.run();
mOnStartCallback = null;
}
}
@@ -238,8 +238,12 @@ public abstract class BaseDraggingActivity extends BaseActivity
mRotationListener.disable();
}
- public void setOnStartCallback(OnStartCallback callback) {
- mOnStartCallback = callback;
+ public void runOnceOnStart(Runnable action) {
+ mOnStartCallback = action;
+ }
+
+ public void clearRunOnceOnStartCallback() {
+ mOnStartCallback = null;
}
protected void onDeviceProfileInitiated() {
@@ -258,12 +262,4 @@ public abstract class BaseDraggingActivity extends BaseActivity
}
protected abstract void reapplyUi();
-
- /**
- * Callback for listening for onStart
- */
- public interface OnStartCallback {
-
- void onActivityStart(T activity);
- }
}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 7adb6a4420..e6f8a85e52 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static com.android.launcher3.FastBitmapDrawable.newIcon;
+import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import android.animation.Animator;
@@ -45,7 +47,6 @@ import com.android.launcher3.Launcher.OnResumeCallback;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.graphics.PreloadIconDrawable;
@@ -215,6 +216,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
cancelDotScaleAnim();
mDotParams.scale = 0f;
mForceHideDot = false;
+ setBackground(null);
}
private void cancelDotScaleAnim() {
@@ -287,9 +289,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
}
private void applyIconAndLabel(ItemInfoWithIcon info) {
- FastBitmapDrawable iconDrawable = DrawableFactory.INSTANCE.get(getContext())
- .newIcon(getContext(), info);
- mDotParams.color = IconPalette.getMutedColor(info.iconColor, 0.54f);
+ FastBitmapDrawable iconDrawable = newIcon(getContext(), info);
+ mDotParams.color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
setIcon(iconDrawable);
setText(info.title);
@@ -496,7 +497,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
// Text should be visible everywhere but the hotseat.
Object tag = getParent() instanceof FolderIcon ? ((View) getParent()).getTag() : getTag();
ItemInfo info = tag instanceof ItemInfo ? (ItemInfo) tag : null;
- return info == null || info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+ return info == null || (info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT
+ && info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION);
}
public void setTextVisibility(boolean visible) {
@@ -567,8 +569,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
preloadDrawable = (PreloadIconDrawable) mIcon;
preloadDrawable.setLevel(progressLevel);
} else {
- preloadDrawable = DrawableFactory.INSTANCE.get(getContext())
- .newPendingIcon(getContext(), info);
+ preloadDrawable = newPendingIcon(getContext(), info);
preloadDrawable.setLevel(progressLevel);
setIcon(preloadDrawable);
}
@@ -665,7 +666,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
mDisableRelayout = true;
// Optimization: Starting in N, pre-uploads the bitmap to RenderThread.
- info.iconBitmap.prepareToDraw();
+ info.bitmap.icon.prepareToDraw();
if (info instanceof AppInfo) {
applyFromApplicationInfo((AppInfo) info);
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 736142f190..a35f598686 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -25,6 +25,8 @@ import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.Surface;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.CellLayout.ContainerType;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.icons.DotRenderer;
@@ -34,6 +36,8 @@ import com.android.launcher3.util.DefaultDisplay;
public class DeviceProfile {
public final InvariantDeviceProfile inv;
+ // IDP with no grid override values.
+ @Nullable private final InvariantDeviceProfile originalIdp;
// Device properties
public final boolean isTablet;
@@ -134,10 +138,11 @@ public class DeviceProfile {
public DotRenderer mDotRendererAllApps;
public DeviceProfile(Context context, InvariantDeviceProfile inv,
- Point minSize, Point maxSize,
+ InvariantDeviceProfile originalIDP, Point minSize, Point maxSize,
int width, int height, boolean isLandscape, boolean isMultiWindowMode) {
this.inv = inv;
+ this.originalIdp = inv;
this.isLandscape = isLandscape;
this.isMultiWindowMode = isMultiWindowMode;
@@ -229,6 +234,19 @@ public class DeviceProfile {
// Recalculate the available dimensions using the new hotseat size.
updateAvailableDimensions(dm, res);
}
+
+ if (originalIDP != null) {
+ // Grid size change should not affect All Apps UI, so we use the original profile
+ // measurements here.
+ DeviceProfile originalProfile = isLandscape
+ ? originalIDP.landscapeProfile
+ : originalIDP.portraitProfile;
+ allAppsIconSizePx = originalProfile.iconSizePx;
+ allAppsIconTextSizePx = originalProfile.iconTextSizePx;
+ allAppsCellHeightPx = originalProfile.allAppsCellHeightPx;
+ allAppsIconDrawablePaddingPx = originalProfile.iconDrawablePaddingOriginalPx;
+ allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
+ }
updateWorkspacePadding();
// This is done last, after iconSizePx is calculated above.
@@ -241,8 +259,8 @@ public class DeviceProfile {
public DeviceProfile copy(Context context) {
Point size = new Point(availableWidthPx, availableHeightPx);
- return new DeviceProfile(context, inv, size, size, widthPx, heightPx, isLandscape,
- isMultiWindowMode);
+ return new DeviceProfile(context, inv, originalIdp, size, size, widthPx, heightPx,
+ isLandscape, isMultiWindowMode);
}
public DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
@@ -253,8 +271,8 @@ public class DeviceProfile {
// In multi-window mode, we can have widthPx = availableWidthPx
// and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
// widthPx and heightPx values where it's needed.
- DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y,
- isLandscape, true);
+ DeviceProfile profile = new DeviceProfile(context, inv, originalIdp, mwSize, mwSize,
+ mwSize.x, mwSize.y, isLandscape, true);
// If there isn't enough vertical cell padding with the labels displayed, hide the labels.
float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx
@@ -338,18 +356,10 @@ public class DeviceProfile {
}
cellWidthPx = iconSizePx + iconDrawablePaddingPx;
- // All apps
- if (allAppsHasDifferentNumColumns()) {
- allAppsIconSizePx = ResourceUtils.pxFromDp(inv.allAppsIconSize, dm);
- allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, dm);
- allAppsCellHeightPx = getCellSize(inv.numAllAppsColumns, inv.numAllAppsColumns).y;
- allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
- } else {
- allAppsIconSizePx = iconSizePx;
- allAppsIconTextSizePx = iconTextSizePx;
- allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
- allAppsCellHeightPx = getCellSize().y;
- }
+ allAppsIconSizePx = iconSizePx;
+ allAppsIconTextSizePx = iconTextSizePx;
+ allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
+ allAppsCellHeightPx = getCellSize().y;
allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
if (isVerticalBarLayout()) {
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index a90025e97f..50916840d9 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -20,6 +20,7 @@ import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import android.animation.ObjectAnimator;
+import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -35,6 +36,7 @@ import android.graphics.drawable.Drawable;
import android.util.Property;
import android.util.SparseArray;
+import com.android.launcher3.graphics.PlaceHolderIconDrawable;
import com.android.launcher3.icons.BitmapInfo;
public class FastBitmapDrawable extends Drawable {
@@ -98,10 +100,6 @@ public class FastBitmapDrawable extends Drawable {
this(info.icon, info.color);
}
- public FastBitmapDrawable(ItemInfoWithIcon info) {
- this(info.iconBitmap, info.iconColor);
- }
-
protected FastBitmapDrawable(Bitmap b, int iconColor) {
this(b, iconColor, false);
}
@@ -365,7 +363,7 @@ public class FastBitmapDrawable extends Drawable {
}
@Override
- public Drawable newDrawable() {
+ public FastBitmapDrawable newDrawable() {
return new FastBitmapDrawable(mBitmap, mIconColor, mIsDisabled);
}
@@ -374,4 +372,37 @@ public class FastBitmapDrawable extends Drawable {
return 0;
}
}
+
+ /**
+ * Interface to be implemented by custom {@link BitmapInfo} to handle drawable construction
+ */
+ public interface Factory {
+
+ /**
+ * Called to create a new drawable
+ */
+ FastBitmapDrawable newDrawable();
+ }
+
+ /**
+ * Returns a FastBitmapDrawable with the icon.
+ */
+ public static FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) {
+ FastBitmapDrawable drawable = newIcon(context, info.bitmap);
+ drawable.setIsDisabled(info.isDisabled());
+ return drawable;
+ }
+
+ /**
+ * Creates a drawable for the provided BitmapInfo
+ */
+ public static FastBitmapDrawable newIcon(Context context, BitmapInfo info) {
+ if (info instanceof Factory) {
+ return ((Factory) info).newDrawable();
+ } else if (info.isLowRes()) {
+ return new PlaceHolderIconDrawable(info, context);
+ } else {
+ return new FastBitmapDrawable(info);
+ }
+ }
}
diff --git a/src/com/android/launcher3/IconProvider.java b/src/com/android/launcher3/IconProvider.java
deleted file mode 100644
index 0f006f7c19..0000000000
--- a/src/com/android/launcher3/IconProvider.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.android.launcher3;
-
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
-
-import android.content.pm.LauncherActivityInfo;
-import android.graphics.drawable.Drawable;
-
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-public class IconProvider implements ResourceBasedOverride {
-
- public static MainThreadInitializedObject INSTANCE =
- forOverride(IconProvider.class, R.string.icon_provider_class);
-
- public IconProvider() { }
-
- public String getSystemStateForPackage(String systemState, String packageName) {
- return systemState;
- }
-
- /**
- * @param flattenDrawable true if the caller does not care about the specification of the
- * original icon as long as the flattened version looks the same.
- */
- public Drawable getIcon(LauncherActivityInfo info, int iconDpi, boolean flattenDrawable) {
- return info.getIcon(iconDpi);
- }
-}
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 0b79dd2835..21359f1dc3 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -22,6 +22,7 @@ import static com.android.launcher3.util.ShortcutUtil.fetchAndUpdateShortcutIcon
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -40,9 +41,11 @@ import android.util.Base64;
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.LauncherIcons;
@@ -239,11 +242,6 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
return info == null ? null : (WorkspaceItemInfo) info.getItemInfo().first;
}
- public static WorkspaceItemInfo fromActivityInfo(LauncherActivityInfo info, Context context) {
- return (WorkspaceItemInfo)
- new PendingInstallShortcutInfo(info, context).getItemInfo().first;
- }
-
public static void queueShortcut(ShortcutInfo info, Context context) {
queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context);
}
@@ -319,10 +317,10 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
private static class PendingInstallShortcutInfo {
final boolean isActivity;
- final ShortcutInfo shortcutInfo;
- final AppWidgetProviderInfo providerInfo;
+ @Nullable final ShortcutInfo shortcutInfo;
+ @Nullable final AppWidgetProviderInfo providerInfo;
- final Intent data;
+ @Nullable final Intent data;
final Context mContext;
final Intent launchIntent;
final String label;
@@ -352,7 +350,12 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
shortcutInfo = null;
providerInfo = null;
- data = null;
+ String packageName = info.getComponentName().getPackageName();
+ data = new Intent();
+ data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent().setComponent(
+ new ComponentName(packageName, "")).setPackage(packageName));
+ data.putExtra(Intent.EXTRA_SHORTCUT_NAME, info.getLabel());
+
user = info.getUser();
mContext = context;
@@ -446,21 +449,26 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
// This name is only used for comparisons and notifications, so fall back to activity
// name if not supplied
String name = ensureValidName(mContext, launchIntent, label).toString();
- Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
- Intent.ShortcutIconResource iconResource =
- data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+ Bitmap icon = data == null ? null
+ : data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+ Intent.ShortcutIconResource iconResource = data == null ? null
+ : data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
// Only encode the parameters which are supported by the API.
JSONStringer json = new JSONStringer()
.object()
.key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
.key(NAME_KEY).value(name)
+ .key(USER_HANDLE_KEY).value(
+ UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user))
.key(APP_SHORTCUT_TYPE_KEY).value(isActivity);
if (icon != null) {
byte[] iconByteArray = GraphicsUtils.flattenBitmap(icon);
- json = json.key(ICON_KEY).value(
- Base64.encodeToString(
- iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
+ if (iconByteArray != null) {
+ json = json.key(ICON_KEY).value(
+ Base64.encodeToString(
+ iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
+ }
}
if (iconResource != null) {
json = json.key(ICON_RESOURCE_NAME_KEY).value(iconResource.resourceName);
@@ -476,14 +484,20 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
public Pair getItemInfo() {
if (isActivity) {
- WorkspaceItemInfo si = createWorkspaceItemInfo(data,
+ WorkspaceItemInfo si = createWorkspaceItemInfo(data, user,
LauncherAppState.getInstance(mContext));
si.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
si.status |= WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
return Pair.create(si, null);
} else if (shortcutInfo != null) {
WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(shortcutInfo, mContext);
- fetchAndUpdateShortcutIconAsync(mContext, itemInfo, shortcutInfo, true);
+ if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
+ fetchAndUpdateShortcutIconAsync(mContext, itemInfo, shortcutInfo, true);
+ } else {
+ LauncherIcons li = LauncherIcons.obtain(mContext);
+ itemInfo.bitmap = li.createShortcutIcon(shortcutInfo);
+ li.recycle();
+ }
return Pair.create(itemInfo, shortcutInfo);
} else if (providerInfo != null) {
LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
@@ -499,7 +513,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
return Pair.create(widgetInfo, providerInfo);
} else {
WorkspaceItemInfo itemInfo =
- createWorkspaceItemInfo(data, LauncherAppState.getInstance(mContext));
+ createWorkspaceItemInfo(data, user, LauncherAppState.getInstance(mContext));
return Pair.create(itemInfo, null);
}
}
@@ -617,7 +631,8 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
return new PendingInstallShortcutInfo(info, original.mContext);
}
- private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, LauncherAppState app) {
+ private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, UserHandle user,
+ LauncherAppState app) {
if (data == null) {
Log.e(TAG, "Can't construct WorkspaceItemInfo with null data");
return null;
@@ -634,10 +649,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
}
final WorkspaceItemInfo info = new WorkspaceItemInfo();
-
- // Only support intents for current user for now. Intents sent from other
- // users wouldn't get here without intent forwarding anyway.
- info.user = Process.myUserHandle();
+ info.user = user;
BitmapInfo iconInfo = null;
LauncherIcons li = LauncherIcons.obtain(app.getContext());
@@ -655,7 +667,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
if (iconInfo == null) {
iconInfo = app.getIconCache().getDefaultIcon(info.user);
}
- info.applyFrom(iconInfo);
+ info.bitmap = iconInfo;
info.title = Utilities.trim(name);
info.contentDescription = app.getContext().getPackageManager()
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 310a9e92ff..9d87152a87 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -58,6 +58,7 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
public class InvariantDeviceProfile {
@@ -103,8 +104,6 @@ public class InvariantDeviceProfile {
public int iconBitmapSize;
public int fillResIconDpi;
public float iconTextSize;
- public float allAppsIconSize;
- public float allAppsIconTextSize;
private SparseArray mExtraAttrs;
@@ -145,8 +144,6 @@ public class InvariantDeviceProfile {
iconTextSize = p.iconTextSize;
numHotseatIcons = p.numHotseatIcons;
numAllAppsColumns = p.numAllAppsColumns;
- allAppsIconSize = p.allAppsIconSize;
- allAppsIconTextSize = p.allAppsIconTextSize;
defaultLayoutId = p.defaultLayoutId;
demoModeLayoutId = p.demoModeLayoutId;
mExtraAttrs = p.mExtraAttrs;
@@ -191,54 +188,11 @@ public class InvariantDeviceProfile {
Point smallestSize = new Point(displayInfo.smallestSize);
Point largestSize = new Point(displayInfo.largestSize);
- ArrayList allOptions = getPredefinedDeviceProfiles(context, gridName);
// This guarantees that width < height
float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y),
displayInfo.metrics);
float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y),
displayInfo.metrics);
- // Sort the profiles based on the closeness to the device size
- Collections.sort(allOptions, (a, b) ->
- Float.compare(dist(minWidthDps, minHeightDps, a.minWidthDps, a.minHeightDps),
- dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps)));
- DisplayOption interpolatedDisplayOption =
- invDistWeightedInterpolate(minWidthDps, minHeightDps, allOptions);
-
- GridOption closestProfile = allOptions.get(0).grid;
- numRows = closestProfile.numRows;
- numColumns = closestProfile.numColumns;
- numHotseatIcons = closestProfile.numHotseatIcons;
- defaultLayoutId = closestProfile.defaultLayoutId;
- demoModeLayoutId = closestProfile.demoModeLayoutId;
- numFolderRows = closestProfile.numFolderRows;
- numFolderColumns = closestProfile.numFolderColumns;
- numAllAppsColumns = closestProfile.numAllAppsColumns;
-
- mExtraAttrs = closestProfile.extraAttrs;
-
- if (!closestProfile.name.equals(gridName)) {
- Utilities.getPrefs(context).edit()
- .putString(KEY_IDP_GRID_NAME, closestProfile.name).apply();
- }
-
- iconSize = interpolatedDisplayOption.iconSize;
- iconShapePath = getIconShapePath(context);
- landscapeIconSize = interpolatedDisplayOption.landscapeIconSize;
- iconBitmapSize = ResourceUtils.pxFromDp(iconSize, displayInfo.metrics);
- iconTextSize = interpolatedDisplayOption.iconTextSize;
- fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
-
- if (Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)) {
- allAppsIconSize = interpolatedDisplayOption.allAppsIconSize;
- allAppsIconTextSize = interpolatedDisplayOption.allAppsIconTextSize;
- } else {
- allAppsIconSize = iconSize;
- allAppsIconTextSize = iconTextSize;
- }
-
- // If the partner customization apk contains any grid overrides, apply them
- // Supported overrides: numRows, numColumns, iconSize
- applyPartnerDeviceProfileOverrides(context, displayInfo.metrics);
Point realSize = new Point(displayInfo.realSize);
// The real size never changes. smallSide and largeSide will remain the
@@ -246,10 +200,64 @@ public class InvariantDeviceProfile {
int smallSide = Math.min(realSize.x, realSize.y);
int largeSide = Math.max(realSize.x, realSize.y);
- landscapeProfile = new DeviceProfile(context, this, smallestSize, largestSize,
- largeSide, smallSide, true /* isLandscape */, false /* isMultiWindowMode */);
- portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize,
- smallSide, largeSide, false /* isLandscape */, false /* isMultiWindowMode */);
+ // We want a list of all options as well as the list of filtered options. This allows us
+ // to have a consistent UI for areas that the grid size change should not affect
+ // ie. All Apps should be consistent between grid sizes.
+ ArrayList allOptions = new ArrayList<>();
+ ArrayList filteredOptions = new ArrayList<>();
+ getPredefinedDeviceProfiles(context, gridName, filteredOptions, allOptions);
+
+ if (allOptions.isEmpty() && filteredOptions.isEmpty()) {
+ throw new RuntimeException("No display option with canBeDefault=true");
+ }
+
+ // Sort the profiles based on the closeness to the device size
+ Comparator comparator = (a, b) -> Float.compare(dist(minWidthDps,
+ minHeightDps, a.minWidthDps, a.minHeightDps),
+ dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps));
+
+ // Calculate the device profiles as if there is no grid override.
+ Collections.sort(allOptions, comparator);
+ DisplayOption interpolatedDisplayOption =
+ invDistWeightedInterpolate(minWidthDps, minHeightDps, allOptions);
+ initGridOption(context, allOptions, interpolatedDisplayOption, displayInfo.metrics);
+
+ // Create IDP with no grid override values.
+ InvariantDeviceProfile originalIDP = new InvariantDeviceProfile(this);
+ originalIDP.landscapeProfile = new DeviceProfile(context, this, null, smallestSize,
+ largestSize, largeSide, smallSide, true /* isLandscape */,
+ false /* isMultiWindowMode */);
+ originalIDP.portraitProfile = new DeviceProfile(context, this, null, smallestSize,
+ largestSize, smallSide, largeSide, false /* isLandscape */,
+ false /* isMultiWindowMode */);
+
+ if (filteredOptions.isEmpty()) {
+ filteredOptions = allOptions;
+
+ landscapeProfile = originalIDP.landscapeProfile;
+ portraitProfile = originalIDP.portraitProfile;
+ } else {
+ Collections.sort(filteredOptions, comparator);
+ interpolatedDisplayOption =
+ invDistWeightedInterpolate(minWidthDps, minHeightDps, filteredOptions);
+
+ initGridOption(context, filteredOptions, interpolatedDisplayOption,
+ displayInfo.metrics);
+ numAllAppsColumns = originalIDP.numAllAppsColumns;
+
+ landscapeProfile = new DeviceProfile(context, this, originalIDP, smallestSize,
+ largestSize, largeSide, smallSide, true /* isLandscape */,
+ false /* isMultiWindowMode */);
+ portraitProfile = new DeviceProfile(context, this, originalIDP, smallestSize,
+ largestSize, smallSide, largeSide, false /* isLandscape */,
+ false /* isMultiWindowMode */);
+ }
+
+ GridOption closestProfile = filteredOptions.get(0).grid;
+ if (!closestProfile.name.equals(gridName)) {
+ Utilities.getPrefs(context).edit()
+ .putString(KEY_IDP_GRID_NAME, closestProfile.name).apply();
+ }
// We need to ensure that there is enough extra space in the wallpaper
// for the intended parallax effects
@@ -267,6 +275,33 @@ public class InvariantDeviceProfile {
return closestProfile.name;
}
+ private void initGridOption(Context context, ArrayList options,
+ DisplayOption displayOption, DisplayMetrics metrics) {
+ GridOption closestProfile = options.get(0).grid;
+ numRows = closestProfile.numRows;
+ numColumns = closestProfile.numColumns;
+ numHotseatIcons = closestProfile.numHotseatIcons;
+ defaultLayoutId = closestProfile.defaultLayoutId;
+ demoModeLayoutId = closestProfile.demoModeLayoutId;
+ numFolderRows = closestProfile.numFolderRows;
+ numFolderColumns = closestProfile.numFolderColumns;
+ numAllAppsColumns = numColumns;
+
+ mExtraAttrs = closestProfile.extraAttrs;
+
+ iconSize = displayOption.iconSize;
+ iconShapePath = getIconShapePath(context);
+ landscapeIconSize = displayOption.landscapeIconSize;
+ iconBitmapSize = ResourceUtils.pxFromDp(iconSize, metrics);
+ iconTextSize = displayOption.iconTextSize;
+ fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
+
+ // If the partner customization apk contains any grid overrides, apply them
+ // Supported overrides: numRows, numColumns, iconSize
+ applyPartnerDeviceProfileOverrides(context, metrics);
+ }
+
+
@Nullable
public TypedValue getAttrValue(int attr) {
return mExtraAttrs == null ? null : mExtraAttrs.get(attr);
@@ -344,7 +379,13 @@ public class InvariantDeviceProfile {
}
}
- static ArrayList getPredefinedDeviceProfiles(Context context, String gridName) {
+ /**
+ * @param gridName The current grid name.
+ * @param filteredOptionsOut List filled with all the filtered options based on gridName.
+ * @param allOptionsOut List filled with all the options that can be the default option.
+ */
+ static void getPredefinedDeviceProfiles(Context context, String gridName,
+ ArrayList filteredOptionsOut, ArrayList allOptionsOut) {
ArrayList profiles = new ArrayList<>();
try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
final int depth = parser.getDepth();
@@ -371,26 +412,19 @@ public class InvariantDeviceProfile {
throw new RuntimeException(e);
}
- ArrayList filteredProfiles = new ArrayList<>();
if (!TextUtils.isEmpty(gridName)) {
for (DisplayOption option : profiles) {
if (gridName.equals(option.grid.name)) {
- filteredProfiles.add(option);
+ filteredOptionsOut.add(option);
}
}
}
- if (filteredProfiles.isEmpty()) {
- // No grid found, use the default options
- for (DisplayOption option : profiles) {
- if (option.canBeDefault) {
- filteredProfiles.add(option);
- }
+
+ for (DisplayOption option : profiles) {
+ if (option.canBeDefault) {
+ allOptionsOut.add(option);
}
}
- if (filteredProfiles.isEmpty()) {
- throw new RuntimeException("No display option with canBeDefault=true");
- }
- return filteredProfiles;
}
private int getLauncherIconDensity(int requiredSize) {
@@ -514,8 +548,6 @@ public class InvariantDeviceProfile {
private final int numHotseatIcons;
- private final int numAllAppsColumns;
-
private final int defaultLayoutId;
private final int demoModeLayoutId;
@@ -538,8 +570,6 @@ public class InvariantDeviceProfile {
R.styleable.GridDisplayOption_numFolderRows, numRows);
numFolderColumns = a.getInt(
R.styleable.GridDisplayOption_numFolderColumns, numColumns);
- numAllAppsColumns = a.getInt(
- R.styleable.GridDisplayOption_numAllAppsColumns, numColumns);
a.recycle();
@@ -559,8 +589,6 @@ public class InvariantDeviceProfile {
private float iconSize;
private float iconTextSize;
private float landscapeIconSize;
- private float allAppsIconSize;
- private float allAppsIconTextSize;
DisplayOption(GridOption grid, Context context, AttributeSet attrs) {
this.grid = grid;
@@ -579,10 +607,6 @@ public class InvariantDeviceProfile {
iconSize);
iconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0);
- allAppsIconSize = a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconSize,
- iconSize);
- allAppsIconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconTextSize,
- iconTextSize);
a.recycle();
}
@@ -597,18 +621,14 @@ public class InvariantDeviceProfile {
private DisplayOption multiply(float w) {
iconSize *= w;
landscapeIconSize *= w;
- allAppsIconSize *= w;
iconTextSize *= w;
- allAppsIconTextSize *= w;
return this;
}
private DisplayOption add(DisplayOption p) {
iconSize += p.iconSize;
landscapeIconSize += p.landscapeIconSize;
- allAppsIconSize += p.allAppsIconSize;
iconTextSize += p.iconTextSize;
- allAppsIconTextSize += p.allAppsIconTextSize;
return this;
}
}
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
index 1550bb080d..19414550f8 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -16,10 +16,6 @@
package com.android.launcher3;
-import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON;
-
-import android.graphics.Bitmap;
-
import com.android.launcher3.icons.BitmapInfo;
/**
@@ -30,14 +26,9 @@ public abstract class ItemInfoWithIcon extends ItemInfo {
public static final String TAG = "ItemInfoDebug";
/**
- * A bitmap version of the application icon.
+ * The bitmap for the application icon
*/
- public Bitmap iconBitmap;
-
- /**
- * Dominant color in the {@link #iconBitmap}.
- */
- public int iconColor;
+ public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO;
/**
* Indicates that the icon is disabled due to safe mode restrictions.
@@ -106,8 +97,7 @@ public abstract class ItemInfoWithIcon extends ItemInfo {
protected ItemInfoWithIcon(ItemInfoWithIcon info) {
super(info);
- iconBitmap = info.iconBitmap;
- iconColor = info.iconColor;
+ bitmap = info.bitmap;
runtimeStatusFlags = info.runtimeStatusFlags;
}
@@ -120,12 +110,7 @@ public abstract class ItemInfoWithIcon extends ItemInfo {
* Indicates whether we're using a low res icon
*/
public boolean usingLowResIcon() {
- return iconBitmap == LOW_RES_ICON;
- }
-
- public void applyFrom(BitmapInfo info) {
- iconBitmap = info.icon;
- iconColor = info.color;
+ return bitmap.isLowRes();
}
/**
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 4b4d7939e6..5a5f7c347e 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -31,8 +31,11 @@ import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.logging.LoggerUtils.newTarget;
+import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
+import static com.android.launcher3.popup.SystemShortcut.DISMISS_PREDICTION;
+import static com.android.launcher3.popup.SystemShortcut.INSTALL;
+import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
-import static com.android.launcher3.testing.TestProtocol.CRASH_ADD_CUSTOM_SHORTCUT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -58,6 +61,7 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Parcelable;
import android.os.Process;
@@ -82,6 +86,8 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
+import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore;
@@ -96,6 +102,7 @@ import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.folder.FolderGridOrganizer;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.FolderNameProvider;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.keyboard.CustomActionsPopup;
@@ -111,11 +118,11 @@ import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.qsb.QsbContainerView;
import com.android.launcher3.states.RotationHelper;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.touch.AllAppsSwipeController;
import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
@@ -135,6 +142,7 @@ import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.UiThreadHelper;
import com.android.launcher3.util.ViewOnDrawExecutor;
@@ -166,6 +174,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.Supplier;
+import java.util.stream.Stream;
/**
* Default launcher application.
@@ -267,6 +276,10 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
private ArrayList mOnResumeCallbacks = new ArrayList<>();
+ // Used to notify when an activity launch has been deferred because launcher is not yet resumed
+ // TODO: See if we can remove this later
+ private Runnable mOnDeferredActivityLaunchCallback;
+
private ViewOnDrawExecutor mPendingExecutor;
private LauncherModel mModel;
@@ -290,12 +303,11 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
*/
private PendingRequestArgs mPendingRequestArgs;
// Request id for any pending activity result
- private int mPendingActivityRequestCode = -1;
+ protected int mPendingActivityRequestCode = -1;
public ViewGroupFocusHelper mFocusHandler;
private RotationHelper mRotationHelper;
- private Runnable mCancelTouchController;
final Handler mHandler = new Handler();
private final Runnable mHandleDeferredResume = this::handleDeferredResume;
@@ -346,7 +358,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
mDragController = new DragController(this);
mAllAppsController = new AllAppsTransitionController(this);
mStateManager = new LauncherStateManager(this);
- UiFactory.onCreate(this);
mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
mAppWidgetHost = new LauncherAppWidgetHost(this,
@@ -470,7 +481,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
@Override
public void onEnterAnimationComplete() {
super.onEnterAnimationComplete();
- UiFactory.onEnterAnimationComplete(this);
mAllAppsController.highlightWorkTabIfNecessary();
mRotationHelper.setCurrentTransitionRequest(REQUEST_NONE);
}
@@ -484,7 +494,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
}
mOldConfig.setTo(newConfig);
- UiFactory.onLauncherStateOrResumeChanged(this);
super.onConfigurationChanged(newConfig);
}
@@ -500,7 +509,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
public void reapplyUi() {
if (supportsFakeLandscapeUI()) {
mRotationMode = mStableDeviceProfile == null
- ? RotationMode.NORMAL : UiFactory.getRotationMode(mDeviceProfile);
+ ? RotationMode.NORMAL : getFakeRotationMode(mDeviceProfile);
}
getRootView().dispatchInsets();
getStateManager().reapplyState(true /* cancelCurrentAnimation */);
@@ -563,7 +572,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
if (supportsFakeLandscapeUI() && mDeviceProfile.isVerticalBarLayout()) {
mStableDeviceProfile = mDeviceProfile.inv.portraitProfile;
- mRotationMode = UiFactory.getRotationMode(mDeviceProfile);
+ mRotationMode = getFakeRotationMode(mDeviceProfile);
} else {
mStableDeviceProfile = null;
mRotationMode = RotationMode.NORMAL;
@@ -606,6 +615,10 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
return mStateManager;
}
+ public FolderNameProvider getFolderNameProvider() {
+ return new FolderNameProvider();
+ }
+
@Override
public T findViewById(int id) {
return mLauncherView.findViewById(id);
@@ -929,8 +942,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
NotificationListener.removeNotificationsChangedListener();
getStateManager().moveToRestState();
- UiFactory.onLauncherStateOrResumeChanged(this);
-
// Workaround for b/78520668, explicitly trim memory once UI is hidden
onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
}
@@ -953,7 +964,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
logStopAndResume(Action.Command.RESUME);
getUserEventDispatcher().startSession();
- UiFactory.onLauncherStateOrResumeChanged(this);
AppLaunchTracker.INSTANCE.get(this).onReturnedToHome();
// Process any items that were added while Launcher was away.
@@ -968,15 +978,17 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
DiscoveryBounce.showForHomeIfNeeded(this);
- if (mPendingActivityRequestCode != -1 && isInState(NORMAL)) {
- UiFactory.resetPendingActivityResults(this, mPendingActivityRequestCode);
- }
+ onDeferredResumed();
+ addActivityFlags(ACTIVITY_STATE_DEFERRED_RESUMED);
+
mDeferredResumePending = false;
} else {
mDeferredResumePending = true;
}
}
+ protected void onDeferredResumed() { }
+
private void logStopAndResume(int command) {
int containerType = mStateManager.getState().containerType;
if (containerType == ContainerType.WORKSPACE && mWorkspace != null) {
@@ -1030,12 +1042,14 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
if (mDeferOverlayCallbacks) {
scheduleDeferredCheck();
}
+ addActivityFlags(ACTIVITY_STATE_TRANSITION_ACTIVE);
}
public void onStateSetEnd(LauncherState state) {
getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
getWorkspace().setClipChildren(!state.disablePageClipping);
finishAutoCancelActionMode();
+ removeActivityFlags(ACTIVITY_STATE_TRANSITION_ACTIVE);
}
@Override
@@ -1080,18 +1094,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
}
}
- @Override
- protected void onUserLeaveHint() {
- super.onUserLeaveHint();
- UiFactory.onLauncherStateOrResumeChanged(this);
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
- super.onWindowFocusChanged(hasFocus);
- mStateManager.onWindowFocusChanged();
- }
-
class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks {
public void onScrollChanged(float progress) {
@@ -1155,7 +1157,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
// Setup the drag layer
mDragLayer.setup(mDragController, mWorkspace);
- mCancelTouchController = UiFactory.enableLiveUIChanges(this);
mWorkspace.setup(mDragController);
// Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
@@ -1533,11 +1534,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
mWorkspace.removeFolderListeners();
PluginManagerWrapper.INSTANCE.get(this).removePluginListener(this);
- if (mCancelTouchController != null) {
- mCancelTouchController.run();
- mCancelTouchController = null;
- }
-
// Stop callbacks from LauncherModel
// It's possible to receive onDestroy after a new Launcher activity has
// been created. In this case, don't interfere with the new Launcher.
@@ -1573,10 +1569,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
if (requestCode != -1) {
mPendingActivityRequestCode = requestCode;
}
- if (requestCode == -1
- || !UiFactory.startActivityForResult(this, intent, requestCode, options)) {
- super.startActivityForResult(intent, requestCode, options);
- }
+ super.startActivityForResult(intent, requestCode, options);
}
@Override
@@ -1585,14 +1578,11 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
if (requestCode != -1) {
mPendingActivityRequestCode = requestCode;
}
- if (requestCode == -1 || !UiFactory.startIntentSenderForResult(this, intent, requestCode,
- fillInIntent, flagsMask, flagsValues, extraFlags, options)) {
- try {
- super.startIntentSenderForResult(intent, requestCode,
- fillInIntent, flagsMask, flagsValues, extraFlags, options);
- } catch (IntentSender.SendIntentException e) {
- throw new ActivityNotFoundException();
- }
+ try {
+ super.startIntentSenderForResult(intent, requestCode,
+ fillInIntent, flagsMask, flagsValues, extraFlags, options);
+ } catch (IntentSender.SendIntentException e) {
+ throw new ActivityNotFoundException();
}
}
@@ -1886,7 +1876,10 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
// recents animation into launcher. Defer launching the activity until Launcher is
// next resumed.
addOnResumeCallback(() -> startActivitySafely(v, intent, item, sourceContainer));
- UiFactory.clearSwipeSharedState(this, true /* finishAnimation */);
+ if (mOnDeferredActivityLaunchCallback != null) {
+ mOnDeferredActivityLaunchCallback.run();
+ mOnDeferredActivityLaunchCallback = null;
+ }
return true;
}
@@ -1927,7 +1920,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
// This clears all widget bitmaps from the widget tray
// TODO(hyunyoungs)
}
- UiFactory.onTrimMemory(this, level);
}
@Override
@@ -1947,6 +1939,14 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
mOnResumeCallbacks.add(callback);
}
+ /**
+ * Persistant callback which notifies when an activity launch is deferred because the activity
+ * was not yet resumed.
+ */
+ public void setOnDeferredActivityLaunchCallback(Runnable callback) {
+ mOnDeferredActivityLaunchCallback = callback;
+ }
+
/**
* Implementation of the method from LauncherModel.Callbacks.
*/
@@ -2638,16 +2638,40 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
return super.onKeyUp(keyCode, event);
}
- public static Launcher getLauncher(Context context) {
- return (Launcher) fromContext(context);
+ protected StateHandler[] createStateHandlers() {
+ return new StateHandler[] { getAllAppsController(), getWorkspace() };
}
+ public TouchController[] createTouchControllers() {
+ return new TouchController[] {getDragController(), new AllAppsSwipeController(this)};
+ }
+
+ protected RotationMode getFakeRotationMode(DeviceProfile deviceProfile) {
+ return RotationMode.NORMAL;
+ }
+
+ protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() {
+ return new ScaleAndTranslation(1.1f, 0f, 0f);
+ }
+
+ public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { }
+
+ public void onDragLayerHierarchyChanged() { }
+
@Override
public void returnToHomescreen() {
super.returnToHomescreen();
getStateManager().goToState(LauncherState.NORMAL);
}
+ public Stream getSupportedShortcuts() {
+ return Stream.of(APP_INFO, WIDGETS, INSTALL, DISMISS_PREDICTION);
+ }
+
+ public static Launcher getLauncher(Context context) {
+ return (Launcher) fromContext(context);
+ }
+
/**
* Just a wrapper around the type cast to allow easier tracking of calls.
*/
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index c717d1a4a3..79f48215f9 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -30,12 +30,14 @@ import android.util.Log;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.pm.InstallSessionTracker;
import com.android.launcher3.pm.PackageInstallerCompat;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SecureSettingsObserver;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.launcher3.widget.custom.CustomWidgetManager;
@@ -57,6 +59,7 @@ public class LauncherAppState {
private final InstallSessionTracker mInstallSessionTracker;
private final SimpleBroadcastReceiver mModelChangeReceiver;
+ private final SafeCloseable mCalendarChangeTracker;
public static LauncherAppState getInstance(final Context context) {
return INSTANCE.get(context);
@@ -92,6 +95,10 @@ public class LauncherAppState {
if (FeatureFlags.IS_DOGFOOD_BUILD) {
mModelChangeReceiver.register(mContext, ACTION_FORCE_ROLOAD);
}
+
+ mCalendarChangeTracker = IconProvider.registerIconChangeListener(mContext,
+ mModel::onAppIconChanged, MODEL_EXECUTOR.getHandler());
+
// TODO: remove listener on terminate
FeatureFlags.APP_SEARCH_IMPROVEMENTS.addChangeListener(context, mModel::forceReload);
CustomWidgetManager.INSTANCE.get(mContext)
@@ -143,6 +150,7 @@ public class LauncherAppState {
mContext.unregisterReceiver(mModelChangeReceiver);
mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel);
mInstallSessionTracker.unregister();
+ mCalendarChangeTracker.close();
CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null);
if (mNotificationDotsObserver != null) {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index fc2e953b00..5ff5b04a5a 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -21,6 +21,7 @@ import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
@@ -31,6 +32,7 @@ import android.util.Log;
import android.util.Pair;
import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
@@ -53,7 +55,6 @@ import com.android.launcher3.model.UserLockStateChangedTask;
import com.android.launcher3.pm.InstallSessionTracker;
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.PackageUserKey;
@@ -93,10 +94,6 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
private boolean mModelLoaded;
public boolean isModelLoaded() {
synchronized (mLock) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
- "isModelLoaded: " + mModelLoaded + ", " + mLoaderTask);
- }
return mModelLoaded && mLoaderTask == null;
}
}
@@ -214,9 +211,21 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true));
}
- public void updatePinnedShortcuts(String packageName, List