Merging from ub-launcher3-master @ build 6018909

Test: manual, presubmit on the source branch
http://x20/teams/android-launcher/merge/ub-launcher3-master_6018909.html

Change-Id: I8a1e20d0b175f03a1a05c81749f07ab5314e872a
This commit is contained in:
Hyunyoung Song
2019-11-19 15:02:28 -08:00
220 changed files with 6839 additions and 4119 deletions
+3 -3
View File
@@ -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
+1 -1
View File
@@ -169,7 +169,7 @@
<activity
android:name="com.android.launcher3.settings.SettingsActivity"
android:label="@string/settings_button_text"
android:theme="@android:style/Theme.DeviceDefault.Settings"
android:theme="@style/HomeSettingsTheme"
android:autoRemoveFromRecents="true">
<intent-filter>
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
+1
View File
@@ -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
+1 -1
View File
@@ -22,7 +22,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.launcher3" >
<uses-sdk android:targetSdkVersion="28" android:minSdkVersion="25"/>
<uses-sdk android:targetSdkVersion="29" android:minSdkVersion="25"/>
<application
android:backupAgent="com.android.launcher3.LauncherBackupAgent"
+2
View File
@@ -22,5 +22,7 @@
<string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
<string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
<string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
</resources>
@@ -0,0 +1,46 @@
/**
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.uioverrides;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
import com.android.launcher3.uioverrides.touchcontrollers.LandscapeStatesTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.SysUINavigationMode;
import java.util.ArrayList;
public class QuickstepLauncher extends BaseQuickstepLauncher {
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = true;
@Override
public TouchController[] createTouchControllers() {
ArrayList<TouchController> 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()]);
}
}
@@ -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<TouchController> 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) { }
}
@@ -46,13 +46,13 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> imple
RemoteAnimationProvider {
private static final String TAG = "AppToOverviewAnimationProvider";
private final BaseActivityInterface<T> mHelper;
private final BaseActivityInterface<T> mActivityInterface;
private final int mTargetTaskId;
private IconRecentsView mRecentsView;
private AppToOverviewAnimationListener mAnimationReadyListener;
AppToOverviewAnimationProvider(BaseActivityInterface<T> helper, int targetTaskId) {
mHelper = helper;
AppToOverviewAnimationProvider(BaseActivityInterface<T> activityInterface, int targetTaskId) {
mActivityInterface = activityInterface;
mTargetTaskId = targetTaskId;
}
@@ -68,15 +68,15 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> 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()
@@ -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<AnimatorPlaybackController> 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<RecentsActivity, Boolean> onInitListener) {
return new ActivityInitListener(onInitListener, RecentsActivity.ACTIVITY_TRACKER);
Predicate<Boolean> 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() { }
}
@@ -17,7 +17,7 @@ public abstract class GoActivityInterface<T extends BaseDraggingActivity> implem
BaseActivityInterface<T> {
@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<T extends BaseDraggingActivity> 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<T extends BaseDraggingActivity> 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<T extends BaseDraggingActivity> implem
}
@Override
public void onLaunchTaskFailed(T activity) {
public void onLaunchTaskFailed() {
// Go does not support gestures from one task to another.
}
}
@@ -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<Launcher> {
@Override
public AnimationFactory prepareRecentsUI(Launcher activity,
boolean activityVisible, boolean animateActivity,
public AnimationFactory prepareRecentsUI(boolean activityVisible, boolean animateActivity,
Consumer<AnimatorPlaybackController> callback) {
LauncherState fromState = activity.getStateManager().getState();
activity.<IconRecentsView>getOverviewPanel().setUsingRemoteAnimation(true);
Launcher launcher = getCreatedActivity();
LauncherState fromState = launcher.getStateManager().getState();
launcher.<IconRecentsView>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<Launche
}
@Override
public LauncherInitListener createActivityInitListener(
BiPredicate<Launcher, Boolean> onInitListener) {
return new LauncherInitListener(onInitListener);
public LauncherInitListener createActivityInitListener(Predicate<Boolean> onInitListener) {
return new LauncherInitListener((activity, alreadyOnHome) ->
onInitListener.test(alreadyOnHome));
}
@Override
@@ -105,7 +105,8 @@ public final class LauncherActivityInterface extends GoActivityInterface<Launche
}
@Override
public void onLaunchTaskSuccess(Launcher launcher) {
public void onLaunchTaskSuccess() {
Launcher launcher = getCreatedActivity();
launcher.getStateManager().moveToRestState();
}
}
@@ -13,22 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3;
package com.android.quickstep.util;
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
import com.android.launcher3.Launcher;
import java.util.function.BiPredicate;
public class LauncherInitListenerEx extends LauncherInitListener {
public LauncherInitListenerEx(BiPredicate<Launcher, Boolean> 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 {
}
}
@@ -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<IconRecentsView> CONTENT_ALPHA =
new FloatProperty<IconRecentsView>("contentAlpha") {
@@ -159,22 +163,6 @@ public final class IconRecentsView extends FrameLayout implements Insettable {
private AnimatorSet mLayoutAnimation;
private final ArraySet<View> mLayingOutViews = new ArraySet<>();
private Rect mInsets;
private final RecentsModel.TaskThumbnailChangeListener listener = (taskId, thumbnailData) -> {
ArrayList<TaskItemView> itemViews = getTaskViews();
for (int i = 0, size = itemViews.size(); i < size; i++) {
TaskItemView taskView = itemViews.get(i);
TaskHolder taskHolder = (TaskHolder) mTaskRecyclerView.getChildViewHolder(taskView);
Optional<Task> 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<TaskItemView> itemViews = getTaskViews();
for (int i = 0, size = itemViews.size(); i < size; i++) {
TaskItemView taskView = itemViews.get(i);
TaskHolder taskHolder = (TaskHolder) mTaskRecyclerView.getChildViewHolder(taskView);
Optional<Task> 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);
@@ -44,6 +44,7 @@ public class BaseIconFactory implements AutoCloseable {
private final PackageManager mPm;
private final ColorExtractor mColorExtractor;
private boolean mDisableColorExtractor;
private boolean mBadgeOnLeft = false;
protected final int mFillResIconDpi;
protected final int mIconBitmapSize;
@@ -77,6 +78,7 @@ public class BaseIconFactory implements AutoCloseable {
protected void clear() {
mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
mDisableColorExtractor = false;
mBadgeOnLeft = false;
}
public ShadowGenerator getShadowGenerator() {
@@ -116,7 +118,7 @@ public class BaseIconFactory implements AutoCloseable {
icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f);
}
return BitmapInfo.fromBitmap(icon, mDisableColorExtractor ? null : mColorExtractor);
return BitmapInfo.of(icon, extractColor(icon));
}
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
@@ -183,7 +185,10 @@ public class BaseIconFactory implements AutoCloseable {
bitmap = createIconBitmap(badged, 1f);
}
}
return BitmapInfo.fromBitmap(bitmap, mDisableColorExtractor ? null : mColorExtractor);
int color = extractColor(bitmap);
return icon instanceof BitmapInfo.Extender
? ((BitmapInfo.Extender) icon).getExtendedInfo(bitmap, color, this)
: BitmapInfo.of(bitmap, color);
}
public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) {
@@ -194,6 +199,13 @@ public class BaseIconFactory implements AutoCloseable {
Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
}
/**
* Switches badging to left/right
*/
public void setBadgeOnLeft(boolean badgeOnLeft) {
mBadgeOnLeft = badgeOnLeft;
}
/**
* Sets the background color used for wrapped adaptive icon
*/
@@ -255,8 +267,12 @@ public class BaseIconFactory implements AutoCloseable {
*/
public void badgeWithDrawable(Canvas target, Drawable badge) {
int badgeSize = getBadgeSizeForIconSize(mIconBitmapSize);
badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
mIconBitmapSize, mIconBitmapSize);
if (mBadgeOnLeft) {
badge.setBounds(0, mIconBitmapSize - badgeSize, badgeSize, mIconBitmapSize);
} else {
badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
mIconBitmapSize, mIconBitmapSize);
}
badge.draw(target);
}
@@ -334,6 +350,10 @@ public class BaseIconFactory implements AutoCloseable {
iconDpi);
}
private int extractColor(Bitmap bitmap) {
return mDisableColorExtractor ? 0 : mColorExtractor.findDominantColorByHue(bitmap);
}
/**
* Returns the correct badge size given an icon size
*/
@@ -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() { }
}
}
@@ -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;
@@ -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<T> {
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
+70
View File
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
**
** Copyright 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.
*/
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.launcher3">
<uses-sdk android:targetSdkVersion="29" android:minSdkVersion="25"/>
<!--
Manifest entries specific to Launcher3. This is merged with AndroidManifest-common.xml.
Refer comments around specific entries on how to extend individual components.
-->
<application
android:backupAgent="com.android.launcher3.LauncherBackupAgent"
android:fullBackupOnly="true"
android:fullBackupContent="@xml/backupscheme"
android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher_home"
android:label="@string/derived_app_name"
android:theme="@style/AppTheme"
android:largeHeap="@bool/config_largeHeap"
android:restoreAnyVersion="true"
android:supportsRtl="true" >
<!--
Main launcher activity. When extending only change the name, and keep all the
attributes and intent filters the same
-->
<activity
android:name="com.android.launcher3.uioverrides.QuickstepLauncher"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
android:stateNotNeeded="true"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="unspecified"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:taskAffinity=""
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY"/>
<category android:name="android.intent.category.LAUNCHER_APP" />
</intent-filter>
<meta-data
android:name="com.android.launcher3.grid.control"
android:value="${packageName}.grid_control" />
</activity>
</application>
</manifest>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:inset="@dimen/predicted_icon_background_inset">
<shape>
<solid android:color="?attr/folderFillColor" />
<corners android:radius="@dimen/predicted_icon_background_corner_radius" />
</shape>
</inset>
@@ -28,4 +28,9 @@
<dimen name="swipe_up_fling_min_visible_change">18dp</dimen>
<dimen name="swipe_up_y_overshoot">10dp</dimen>
<dimen name="swipe_up_max_workspace_trans_y">-60dp</dimen>
<!-- Predicted icon related -->
<dimen name="predicted_icon_background_corner_radius">15dp</dimen>
<dimen name="predicted_icon_background_inset">8dp</dimen>
</resources>
@@ -24,5 +24,7 @@
<string name="app_launch_tracker_class" translatable="false">com.android.launcher3.appprediction.PredictionAppTracker</string>
<string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
<string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension</string>
</resources>
@@ -0,0 +1,404 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppPredictionManager;
import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
import android.content.ComponentName;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.appprediction.ComponentKeyMapper;
import com.android.launcher3.appprediction.DynamicItemCache;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.ComponentKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Provides prediction ability for the hotseat. Fills gaps in hotseat with predicted items, allows
* pinning of predicted apps and manages replacement of predicted apps with user drag.
*/
public class HotseatPredictionController implements DragController.DragListener,
View.OnAttachStateChangeListener, SystemShortcut.Factory<QuickstepLauncher>,
InvariantDeviceProfile.OnIDPChangeListener, AllAppsStore.OnUpdateListener,
IconCache.ItemInfoUpdateReceiver {
private static final String TAG = "PredictiveHotseat";
private static final boolean DEBUG = false;
//TODO: replace this with AppTargetEvent.ACTION_UNPIN (b/144119543)
private static final int APPTARGET_ACTION_UNPIN = 4;
private static final String PREDICTION_CLIENT = "hotseat";
private DropTarget.DragObject mDragObject;
private int mHotSeatItemsCount;
private Launcher mLauncher;
private Hotseat mHotseat;
private List<ComponentKeyMapper> mComponentKeyMappers = new ArrayList<>();
private DynamicItemCache mDynamicItemCache;
private AppPredictor mAppPredictor;
private AllAppsStore mAllAppsStore;
public HotseatPredictionController(Launcher launcher) {
mLauncher = launcher;
mHotseat = launcher.getHotseat();
mAllAppsStore = mLauncher.getAppsView().getAppsStore();
mAllAppsStore.addUpdateListener(this);
mDynamicItemCache = new DynamicItemCache(mLauncher, () -> fillGapsWithPrediction(false));
mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
launcher.getDeviceProfile().inv.addOnChangeListener(this);
mHotseat.addOnAttachStateChangeListener(this);
}
@Override
public void onViewAttachedToWindow(View view) {
mLauncher.getDragController().addDragListener(this);
}
@Override
public void onViewDetachedFromWindow(View view) {
mLauncher.getDragController().removeDragListener(this);
}
/**
* Fills gaps in the hotseat with predictions
*/
public void fillGapsWithPrediction(boolean animate) {
if (mDragObject != null) {
return;
}
List<WorkspaceItemInfo> predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
int predictionIndex = 0;
ArrayList<ItemInfo> newItemsToAdd = new ArrayList<>();
for (int rank = 0; rank < mHotSeatItemsCount; rank++) {
View child = mHotseat.getChildAt(
mHotseat.getCellXFromOrder(rank),
mHotseat.getCellYFromOrder(rank));
if (child != null && !isPredictedIcon(child)) {
continue;
}
if (predictedApps.size() <= predictionIndex) {
// Remove predicted apps from the past
if (isPredictedIcon(child)) {
mHotseat.removeView(child);
}
continue;
}
WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++);
if (isPredictedIcon(child)) {
BubbleTextView icon = (BubbleTextView) child;
icon.applyFromWorkspaceItem(predictedItem);
} else {
newItemsToAdd.add(predictedItem);
}
preparePredictionInfo(predictedItem, rank);
}
mLauncher.bindItems(newItemsToAdd, animate);
for (BubbleTextView icon : getPredictedIcons()) {
icon.verifyHighRes();
icon.setOnLongClickListener((v) -> {
PopupContainerWithArrow.showForIcon((BubbleTextView) v);
return true;
});
icon.setBackgroundResource(R.drawable.predicted_icon_background);
}
}
/**
* Unregisters callbacks and frees resources
*/
public void destroy() {
mAllAppsStore.removeUpdateListener(this);
mLauncher.getDeviceProfile().inv.removeOnChangeListener(this);
mHotseat.removeOnAttachStateChangeListener(this);
if (mAppPredictor != null) {
mAppPredictor.destroy();
}
}
/**
* Creates App Predictor with all the current apps pinned on the hotseat
*/
public void createPredictor() {
AppPredictionManager apm = mLauncher.getSystemService(AppPredictionManager.class);
if (apm == null) {
return;
}
if (mAppPredictor != null) {
mAppPredictor.destroy();
}
mAppPredictor = apm.createAppPredictionSession(
new AppPredictionContext.Builder(mLauncher)
.setUiSurface(PREDICTION_CLIENT)
.setPredictedTargetCount(mHotSeatItemsCount)
.setExtras(getAppPredictionContextExtra())
.build());
mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
this::setPredictedApps);
mAppPredictor.requestPredictionUpdate();
}
private Bundle getAppPredictionContextExtra() {
Bundle bundle = new Bundle();
ViewGroup vg = mHotseat.getShortcutsAndWidgets();
ArrayList<AppTarget> pinnedApps = new ArrayList<>();
for (int i = 0; i < vg.getChildCount(); i++) {
View child = vg.getChildAt(i);
if (isPinnedIcon(child)) {
WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) child.getTag();
pinnedApps.add(getAppTargetFromItemInfo(itemInfo));
}
}
bundle.putParcelableArrayList("pinned_apps", pinnedApps);
return bundle;
}
private void setPredictedApps(List<AppTarget> appTargets) {
mComponentKeyMappers.clear();
for (AppTarget appTarget : appTargets) {
ComponentKey key;
if (appTarget.getShortcutInfo() != null) {
key = ShortcutKey.fromInfo(appTarget.getShortcutInfo());
} else {
key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
appTarget.getClassName()), appTarget.getUser());
}
mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
}
updateDependencies();
fillGapsWithPrediction(false);
}
private void updateDependencies() {
mDynamicItemCache.updateDependencies(mComponentKeyMappers, mAllAppsStore, this,
mHotSeatItemsCount);
}
private void pinPrediction(ItemInfo info) {
BubbleTextView icon = (BubbleTextView) mHotseat.getChildAt(
mHotseat.getCellXFromOrder(info.rank),
mHotseat.getCellYFromOrder(info.rank));
if (icon == null) {
return;
}
WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo((WorkspaceItemInfo) info);
mLauncher.getModelWriter().addItemToDatabase(workspaceItemInfo,
LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId,
workspaceItemInfo.cellX, workspaceItemInfo.cellY);
ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start();
icon.reset();
icon.applyFromWorkspaceItem(workspaceItemInfo);
icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
AppTarget appTarget = getAppTargetFromItemInfo(workspaceItemInfo);
notifyItemAction(appTarget, AppTargetEvent.ACTION_PIN);
}
private List<WorkspaceItemInfo> mapToWorkspaceItemInfo(
List<ComponentKeyMapper> components) {
AllAppsStore allAppsStore = mLauncher.getAppsView().getAppsStore();
if (allAppsStore.getApps().length == 0) {
return Collections.emptyList();
}
List<WorkspaceItemInfo> 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<BubbleTextView> getPredictedIcons() {
List<BubbleTextView> icons = new ArrayList<>();
ViewGroup vg = mHotseat.getShortcutsAndWidgets();
for (int i = 0; i < vg.getChildCount(); i++) {
View child = vg.getChildAt(i);
if (isPredictedIcon(child)) {
icons.add((BubbleTextView) child);
}
}
return icons;
}
private void removePredictedApps(boolean animate) {
for (BubbleTextView icon : getPredictedIcons()) {
if (animate) {
icon.animate().scaleY(0).scaleX(0).setListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
if (icon.getParent() != null) {
mHotseat.removeView(icon);
}
}
});
} else {
if (icon.getParent() != null) {
mHotseat.removeView(icon);
}
}
}
}
private void notifyItemAction(AppTarget target, int action) {
if (mAppPredictor != null) {
mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target, action).build());
}
}
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
removePredictedApps(true);
mDragObject = dragObject;
}
@Override
public void onDragEnd() {
if (mDragObject == null) {
return;
}
ItemInfo dragInfo = mDragObject.dragInfo;
if (dragInfo instanceof WorkspaceItemInfo && dragInfo.getTargetComponent() != null) {
if (isInHotseat(dragInfo) && !isInHotseat(mDragObject.originalDragInfo)) {
notifyItemAction(getAppTargetFromItemInfo(dragInfo), AppTargetEvent.ACTION_PIN);
} else if (!isInHotseat(dragInfo) && isInHotseat(mDragObject.originalDragInfo)) {
notifyItemAction(getAppTargetFromItemInfo(dragInfo), APPTARGET_ACTION_UNPIN);
}
}
mDragObject = null;
fillGapsWithPrediction(true);
}
@Nullable
@Override
public SystemShortcut<QuickstepLauncher> 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<QuickstepLauncher> {
private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
super(R.drawable.ic_pin, R.string.pin_prediction, target,
itemInfo);
}
@Override
public void onClick(View view) {
dismissTaskMenuView(mTarget);
pinPrediction(mItemInfo);
}
}
private static boolean isPredictedIcon(View view) {
return view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo
&& ((WorkspaceItemInfo) view.getTag()).container
== LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
}
private static boolean isPinnedIcon(View view) {
if (!(view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo)) {
return false;
}
ItemInfo info = (ItemInfo) view.getTag();
return info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && (
info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
|| info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
}
private static boolean isInHotseat(ItemInfo itemInfo) {
return itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
}
private static AppTarget getAppTargetFromItemInfo(ItemInfo info) {
if (info.getTargetComponent() == null) return null;
return new AppTarget.Builder(
new AppTargetId("app:" + info.getTargetComponent().getPackageName()),
info.getTargetComponent().getPackageName(), info.user).setClassName(
info.getTargetComponent().getClassName()).build();
}
}
@@ -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);
@@ -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;
}
@@ -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<ShortcutKey, WorkspaceItemInfo> mShortcuts;
private final Map<String, InstantAppItemInfo> 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<ComponentKeyMapper> componentKeyMappers,
AllAppsStore appsStore, IconCache.ItemInfoUpdateReceiver callback, int itemCount) {
List<String> instantAppsToLoad = new ArrayList<>();
List<ShortcutKey> 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);
}
}
@@ -1,4 +1,4 @@
/**
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,33 +18,36 @@ package com.android.launcher3.appprediction;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.content.ComponentName;
import android.content.Context;
import com.android.launcher3.AppInfo;
import androidx.annotation.NonNull;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.StateListener;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.MainThreadInitializedObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;
/**
* Handler responsible to updating the UI due to predicted apps changes. Operations:
@@ -239,7 +242,7 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe
key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
appTarget.getClassName()), appTarget.getUser());
}
state.apps.add(new ComponentKeyMapper(mContext, key, mDynamicItemCache));
state.apps.add(new ComponentKeyMapper(key, mDynamicItemCache));
}
}
updateDependencies(state);
@@ -250,33 +253,8 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe
if (!state.isEnabled || mAppsView == null) {
return;
}
IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
List<String> instantAppsToLoad = new ArrayList<>();
List<ShortcutKey> shortcutsToLoad = new ArrayList<>();
int total = state.apps.size();
for (int i = 0, count = 0; i < total && count < mMaxIconsPerRow; i++) {
ComponentKeyMapper mapper = state.apps.get(i);
// Update instant apps
if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) {
instantAppsToLoad.add(mapper.getPackage());
count++;
} else if (mapper.getComponentKey() instanceof ShortcutKey) {
shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey());
count++;
} else {
// Reload high res icon
AppInfo info = (AppInfo) mapper.getApp(mAppsView.getAppsStore());
if (info != null) {
if (info.usingLowResIcon()) {
// TODO: Update icon cache to support null callbacks.
iconCache.updateIconInBackground(this, info);
}
count++;
}
}
}
mDynamicItemCache.cacheItems(shortcutsToLoad, instantAppsToLoad);
mDynamicItemCache.updateDependencies(state.apps, mAppsView.getAppsStore(), this,
mMaxIconsPerRow);
}
@Override
@@ -322,6 +300,30 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe
return mCurrentState;
}
/**
* Fill in predicted_rank field based on app prediction.
* Only applicable when {@link ItemInfo#itemType} is one of the followings:
* {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
* {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
* {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT}
*/
public static void fillInPredictedRank(
@NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate();
if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null
|| (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
&& itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
&& itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
return;
}
final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
final List<ComponentKeyMapper> predictedApps = manager.getCurrentState().apps;
IntStream.range(0, predictedApps.size())
.filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
.findFirst()
.ifPresent((rank) -> target.predictedRank = rank);
}
public static class PredictionState {
public boolean isEnabled;
@@ -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<TouchController> 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.<RecentsView>getOverviewPanel().switchToScreenshot(
() -> TouchInteractionService.getSwipeSharedState().clearAllState(
finishAnimation));
@Override
public Stream<SystemShortcut.Factory> 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.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(false);
((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false);
}
}
@Override
public void finishBindingItems(int pageBoundFirst) {
super.finishBindingItems(pageBoundFirst);
if (mHotseatPredictionController != null) {
mHotseatPredictionController.createPredictor();
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mHotseatPredictionController != null) {
mHotseatPredictionController.destroy();
}
}
@Override
public TouchController[] createTouchControllers() {
Mode mode = SysUINavigationMode.getMode(this);
ArrayList<TouchController> 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<Launcher> {
@@ -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;
}
@@ -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
@@ -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 {
@@ -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();
@@ -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();
@@ -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.<RecentsView>getOverviewPanel().getTaskViewAt(0);
ActivityManagerWrapper.getInstance()
.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
}
@Override
@@ -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<T extends BaseDraggingActivity>
extends AnimatorListenerAdapter implements TouchController, SwipeDetector.Listener {
private static final String TAG = "OverviewSwipeController";
extends AnimatorListenerAdapter implements TouchController,
SingleAxisSwipeDetector.Listener {
// Progress after which the transition is assumed to be a success in case user does not fling
public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
protected final T mActivity;
private final SwipeDetector mDetector;
private final SingleAxisSwipeDetector mDetector;
private final RecentsView mRecentsView;
private final int[] mTempCords = new int[2];
@@ -74,7 +77,7 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
}
@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<T extends BaseDraggingActivity>
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);
@@ -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
@@ -70,7 +70,7 @@ final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> imple
activity.<RecentsView>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<T extends BaseDraggingActivity> imple
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
mHelper.onSwipeUpToRecentsComplete(mActivity);
mHelper.onSwipeUpToRecentsComplete();
if (mRecentsView != null) {
mRecentsView.animateUpRunningTaskIconScale();
}
@@ -15,20 +15,15 @@
*/
package com.android.quickstep;
import static android.os.VibrationEffect.EFFECT_CLICK;
import static android.os.VibrationEffect.createPredefined;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import android.animation.Animator;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.Intent;
import android.graphics.Point;
@@ -36,11 +31,6 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.Settings;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
@@ -55,15 +45,15 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AppWindowAnimationHelper;
import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -96,17 +86,14 @@ public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q exten
protected float mDragLengthFactor = 1;
protected final Context mContext;
protected final OverviewComponentObserver mOverviewComponentObserver;
protected final RecentsAnimationDeviceState mDeviceState;
protected final GestureState mGestureState;
protected final BaseActivityInterface<T> 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<T extends BaseDraggingActivity, Q exten
protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
protected final ActivityInitListener mActivityInitListener;
protected final InputConsumerController mInputConsumer;
protected RecentsAnimationController mRecentsAnimationController;
protected RecentsAnimationTargets mRecentsAnimationTargets;
@@ -129,54 +115,30 @@ public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q exten
protected Runnable mGestureEndCallback;
protected final Handler mMainThreadHandler = MAIN_EXECUTOR.getHandler();
protected MultiStateCallback mStateCallback;
protected boolean mCanceled;
protected int mFinishingRecentsAnimationForNewTaskId = -1;
protected BaseSwipeUpHandler(Context context, GestureState gestureState,
OverviewComponentObserver overviewComponentObserver,
RecentsModel recentsModel, InputConsumerController inputConsumer, int runningTaskId) {
protected BaseSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
GestureState gestureState, InputConsumerController inputConsumer) {
mContext = context;
mOverviewComponentObserver = overviewComponentObserver;
mDeviceState = deviceState;
mGestureState = gestureState;
mActivityInterface = gestureState.getActivityInterface();
mRecentsModel = recentsModel;
mActivityInitListener =
mActivityInterface.createActivityInitListener(this::onActivityInit);
mRunningTaskId = runningTaskId;
mInputConsumer = inputConsumer;
mMode = SysUINavigationMode.getMode(context);
mAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
mVibrator = context.getSystemService(Vibrator.class);
initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext)
.getDeviceProfile(mContext));
}
protected void setStateOnUiThread(int stateFlag) {
if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
mStateCallback.setState(stateFlag);
} else {
postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag));
}
}
protected void performHapticFeedback() {
if (!mVibrator.hasVibrator()) {
return;
}
if (Settings.System.getInt(
mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) == 0) {
return;
}
VibrationEffect effect = createPredefined(EFFECT_CLICK);
if (effect == null) {
return;
}
UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(effect));
VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
}
public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode rotationMode) {
@@ -246,14 +208,14 @@ public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q exten
success -> {
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<T extends BaseDraggingActivity, Q exten
mRecentsAnimationTargets = targets;
DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
final Rect overviewStackBounds;
RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mRunningTaskId);
RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
mGestureState.getRunningTaskId());
if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
overviewStackBounds = mActivityInterface
@@ -322,12 +285,18 @@ public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q exten
public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
mRecentsAnimationController = null;
mRecentsAnimationTargets = null;
if (mRecentsView != null) {
mRecentsView.setRecentsAnimationTargets(null, null);
}
}
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
mRecentsAnimationController = null;
mRecentsAnimationTargets = null;
if (mRecentsView != null) {
mRecentsView.setRecentsAnimationTargets(null, null);
}
}
private Rect getStackBounds(DeviceProfile dp) {
@@ -355,7 +324,7 @@ public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q exten
mAppWindowAnimationHelper.updateHomeBounds(getStackBounds(dp));
}
mAppWindowAnimationHelper.updateTargetRect(TEMP_RECT);
if (mMode == Mode.NO_BUTTON) {
if (mDeviceState.isFullyGesturalNavMode()) {
// We can drag all the way to the top of the screen.
mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
}
@@ -366,7 +335,7 @@ public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q exten
*/
protected abstract boolean moveWindowWithRecentsScroll();
protected abstract boolean onActivityInit(final T activity, Boolean alreadyOnHome);
protected abstract boolean onActivityInit(Boolean alreadyOnHome);
/**
* Called to create a input proxy for the running task
@@ -394,13 +363,13 @@ public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q exten
@UiThread
public abstract void onGestureEnded(float endVelocity, PointF velocity, PointF downPos);
public abstract void onConsumerAboutToBeSwitched(SwipeSharedState sharedState);
public abstract void onConsumerAboutToBeSwitched();
public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { }
public void initWhenReady() {
// Preload the plan
mRecentsModel.getTasks(null);
RecentsModel.INSTANCE.get(mContext).getTasks(null);
mActivityInitListener.register();
}
@@ -517,8 +486,8 @@ public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q exten
public interface Factory {
BaseSwipeUpHandler newHandler(GestureState gestureState, RunningTaskInfo runningTask,
long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask);
BaseSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs,
boolean continuingLastGesture, boolean isLikelyToStartNewTask);
}
protected interface RunningWindowAnim {
@@ -30,6 +30,7 @@ import android.graphics.RectF;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -40,8 +41,8 @@ import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
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 recents when the default launcher is different than the
@@ -54,7 +55,7 @@ public final class FallbackActivityInterface implements
public FallbackActivityInterface() { }
@Override
public void onTransitionCancelled(RecentsActivity activity, boolean activityVisible) {
public void onTransitionCancelled(boolean activityVisible) {
// TODO:
}
@@ -72,7 +73,11 @@ public final class FallbackActivityInterface implements
}
@Override
public void onSwipeUpToRecentsComplete(RecentsActivity activity) {
public void onSwipeUpToRecentsComplete() {
RecentsActivity activity = getCreatedActivity();
if (activity == null) {
return;
}
RecentsView recentsView = activity.getOverviewPanel();
recentsView.getClearAllButton().setVisibilityAlpha(1);
recentsView.setDisallowScrollToClearAll(false);
@@ -87,7 +92,8 @@ public final class FallbackActivityInterface implements
@NonNull
@Override
public HomeAnimationFactory prepareHomeUI(RecentsActivity activity) {
public HomeAnimationFactory prepareHomeUI() {
RecentsActivity activity = getCreatedActivity();
RecentsView recentsView = activity.getOverviewPanel();
return new HomeAnimationFactory() {
@@ -118,8 +124,9 @@ public final class FallbackActivityInterface implements
}
@Override
public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible,
public AnimationFactory prepareRecentsUI(boolean activityVisible,
boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
RecentsActivity activity = getCreatedActivity();
if (activityVisible) {
return (transitionLength) -> { };
}
@@ -176,8 +183,9 @@ public final class FallbackActivityInterface implements
@Override
public ActivityInitListener createActivityInitListener(
BiPredicate<RecentsActivity, Boolean> onInitListener) {
return new ActivityInitListener(onInitListener, RecentsActivity.ACTIVITY_TRACKER);
Predicate<Boolean> 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.<RecentsView>getOverviewPanel().startHome();
}
@Override
public void onLaunchTaskSuccess(RecentsActivity activity) {
public void onLaunchTaskSuccess() {
RecentsActivity activity = getCreatedActivity();
if (activity == null) {
return;
}
activity.onTaskLaunched();
}
}
@@ -13,54 +13,46 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.inputconsumers;
package com.android.quickstep;
import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
import static com.android.quickstep.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID;
import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL;
import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.HOME;
import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.NEW_TASK;
import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.RECENTS;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Bundle;
import android.util.ArrayMap;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.util.ObjectWrapper;
import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.BaseSwipeUpHandler;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.MultiStateCallback;
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.RecentsAnimationController;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.SwipeSharedState;
import com.android.quickstep.GestureState.GestureEndTarget;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.RecentsAnimationTargets;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.InputConsumerController;
public class FallbackNoButtonInputConsumer extends
BaseSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
/**
* Handles the navigation gestures when a 3rd party launcher is the default home activity.
*/
public class FallbackSwipeHandler extends BaseSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
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<GestureEndTarget, EndTargetAnimationParams>
mEndTargetAnimationParams = new ArrayMap();
private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged);
private boolean mIsMotionPaused = false;
private GestureEndTarget mEndTarget;
private final boolean mInQuickSwitchMode;
private final boolean mContinuingLastGesture;
private final boolean mRunningOverHome;
private final boolean mSwipeUpOverHome;
private final RunningTaskInfo mRunningTaskInfo;
private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f);
private RunningWindowAnim mFinishAnimation;
public FallbackNoButtonInputConsumer(Context context, GestureState gestureState,
OverviewComponentObserver overviewComponentObserver,
RunningTaskInfo runningTaskInfo, RecentsModel recentsModel,
InputConsumerController inputConsumer,
public FallbackSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
GestureState gestureState, InputConsumerController inputConsumer,
boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
super(context, gestureState, overviewComponentObserver, recentsModel, inputConsumer,
runningTaskInfo.id);
super(context, deviceState, gestureState, inputConsumer);
mLauncherAlpha.value = 1;
mRunningTaskInfo = runningTaskInfo;
mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
mContinuingLastGesture = continuingLastGesture;
mRunningOverHome = ActivityManagerWrapper.isHomeTask(runningTaskInfo);
mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask());
mSwipeUpOverHome = mRunningOverHome && !mInQuickSwitchMode;
if (mSwipeUpOverHome) {
@@ -136,40 +118,46 @@ public class FallbackNoButtonInputConsumer extends
mAppWindowAnimationHelper.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value);
}
// Going home has an extra long progress to ensure that it animates into the screen
mEndTargetAnimationParams.put(HOME, new EndTargetAnimationParams(3, 100, 1));
mEndTargetAnimationParams.put(RECENTS, new EndTargetAnimationParams(1, 300, 0));
mEndTargetAnimationParams.put(LAST_TASK, new EndTargetAnimationParams(0, 150, 1));
mEndTargetAnimationParams.put(NEW_TASK, new EndTargetAnimationParams(0, 150, 1));
initStateCallbacks();
}
private void initStateCallbacks() {
mStateCallback = new MultiStateCallback(STATE_NAMES);
mStateCallback.addCallback(STATE_HANDLER_INVALIDATED,
mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED,
this::onHandlerInvalidated);
mStateCallback.addCallback(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED,
mStateCallback.runOnceAtState(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED,
this::onHandlerInvalidatedWithRecents);
mStateCallback.addCallback(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED,
mStateCallback.runOnceAtState(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED,
this::finishAnimationTargetSetAnimationComplete);
if (mInQuickSwitchMode) {
mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED
mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED
| STATE_RECENTS_PRESENT,
this::finishAnimationTargetSet);
} else {
mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED,
mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED,
this::finishAnimationTargetSet);
}
}
private void onLauncherAlphaChanged() {
if (mRecentsAnimationTargets != null && mEndTarget == null) {
if (mRecentsAnimationTargets != null && mGestureState.getEndTarget() == null) {
applyTransformUnchecked();
}
}
@Override
protected boolean onActivityInit(final RecentsActivity activity, Boolean alreadyOnHome) {
mActivity = activity;
mRecentsView = activity.getOverviewPanel();
protected boolean onActivityInit(Boolean alreadyOnHome) {
mActivity = mActivityInterface.getCreatedActivity();
mRecentsView = mActivity.getOverviewPanel();
linkRecentsViewScroll();
mRecentsView.setDisallowScrollToClearAll(true);
mRecentsView.getClearAllButton().setVisibilityAlpha(0);
@@ -178,12 +166,12 @@ public class FallbackNoButtonInputConsumer extends
if (!mContinuingLastGesture) {
if (mRunningOverHome) {
mRecentsView.onGestureAnimationStart(mRunningTaskInfo);
mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask());
} else {
mRecentsView.onGestureAnimationStart(mRunningTaskId);
mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId());
}
}
setStateOnUiThread(STATE_RECENTS_PRESENT);
mStateCallback.setStateOnUiThread(STATE_RECENTS_PRESENT);
return true;
}
@@ -226,9 +214,9 @@ public class FallbackNoButtonInputConsumer extends
@Override
public Intent getLaunchIntent() {
if (mInQuickSwitchMode || mSwipeUpOverHome) {
return mOverviewComponentObserver.getOverviewIntent();
return mGestureState.getOverviewIntent();
} else {
return mOverviewComponentObserver.getHomeIntent();
return mGestureState.getHomeIntent();
}
}
@@ -247,8 +235,8 @@ public class FallbackNoButtonInputConsumer extends
@Override
public void onGestureCancelled() {
updateDisplacement(0);
mEndTarget = LAST_TASK;
setStateOnUiThread(STATE_GESTURE_CANCELLED);
mGestureState.setEndTarget(LAST_TASK);
mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED);
}
@Override
@@ -256,28 +244,29 @@ public class FallbackNoButtonInputConsumer extends
mEndVelocityPxPerMs.set(0, velocity.y / 1000);
if (mInQuickSwitchMode) {
// For now set it to non-null, it will be reset before starting the animation
mEndTarget = LAST_TASK;
mGestureState.setEndTarget(LAST_TASK);
} else {
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
boolean isFling = Math.abs(endVelocity) > flingThreshold;
if (isFling) {
mEndTarget = endVelocity < 0 ? HOME : LAST_TASK;
mGestureState.setEndTarget(endVelocity < 0 ? HOME : LAST_TASK);
} else if (mIsMotionPaused) {
mEndTarget = RECENTS;
mGestureState.setEndTarget(RECENTS);
} else {
mEndTarget = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? HOME : LAST_TASK;
mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW
? HOME
: LAST_TASK);
}
}
setStateOnUiThread(STATE_GESTURE_COMPLETED);
mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
}
@Override
public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) {
if (mInQuickSwitchMode && mEndTarget != null) {
sharedState.canGestureBeContinued = true;
sharedState.goingToLauncher = false;
public void onConsumerAboutToBeSwitched() {
if (mInQuickSwitchMode && mGestureState.getEndTarget() != null) {
mGestureState.setEndTarget(HOME);
mCanceled = true;
mCurrentShift.cancelAnimation();
@@ -293,12 +282,12 @@ public class FallbackNoButtonInputConsumer extends
? newRunningTaskView.getTask().key.id
: -1;
mRecentsView.setCurrentTask(newRunningTaskId);
sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId);
mGestureState.setFinishingRecentsAnimationTaskId(newRunningTaskId);
}
mRecentsView.setOnScrollChangeListener(null);
}
} else {
setStateOnUiThread(STATE_HANDLER_INVALIDATED);
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
}
@@ -319,12 +308,12 @@ public class FallbackNoButtonInputConsumer extends
}
private void finishAnimationTargetSetAnimationComplete() {
switch (mEndTarget) {
switch (mGestureState.getEndTarget()) {
case HOME: {
if (mSwipeUpOverHome) {
mRecentsAnimationController.finish(false, null, false);
// Send a home intent to clear the task stack
mContext.startActivity(mOverviewComponentObserver.getHomeIntent());
mContext.startActivity(mGestureState.getHomeIntent());
} else {
mRecentsAnimationController.finish(true, null, true);
}
@@ -339,7 +328,8 @@ public class FallbackNoButtonInputConsumer extends
break;
}
ThumbnailData thumbnail = mRecentsAnimationController.screenshotTask(mRunningTaskId);
final int runningTaskId = mGestureState.getRunningTaskId();
ThumbnailData thumbnail = mRecentsAnimationController.screenshotTask(runningTaskId);
mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
false /* screenshot */);
@@ -348,9 +338,9 @@ public class FallbackNoButtonInputConsumer extends
Bundle extras = new Bundle();
extras.putBinder(EXTRA_THUMBNAIL, new ObjectWrapper<>(thumbnail));
extras.putInt(EXTRA_TASK_ID, mRunningTaskId);
extras.putInt(EXTRA_TASK_ID, runningTaskId);
Intent intent = new Intent(mOverviewComponentObserver.getOverviewIntent())
Intent intent = new Intent(mGestureState.getOverviewIntent())
.putExtras(extras);
mContext.startActivity(intent, options.toBundle());
mRecentsAnimationController.cleanupScreenshot();
@@ -362,7 +352,7 @@ public class FallbackNoButtonInputConsumer extends
}
}
setStateOnUiThread(STATE_HANDLER_INVALIDATED);
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
private void finishAnimationTargetSet() {
@@ -370,17 +360,20 @@ public class FallbackNoButtonInputConsumer extends
// Recalculate the end target, some views might have been initialized after
// gesture has ended.
if (mRecentsView == null || !hasTargets()) {
mEndTarget = LAST_TASK;
mGestureState.setEndTarget(LAST_TASK);
} else {
final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
final int taskToLaunch = mRecentsView.getNextPage();
mEndTarget = (runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex)
? NEW_TASK : LAST_TASK;
mGestureState.setEndTarget(
(runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex)
? NEW_TASK
: LAST_TASK);
}
}
float endProgress = mEndTarget.mEndProgress;
long duration = (long) (mEndTarget.mDurationMultiplier *
EndTargetAnimationParams params = mEndTargetAnimationParams.get(mGestureState.getEndTarget());
float endProgress = params.mEndProgress;
long duration = (long) (params.mDurationMultiplier *
Math.abs(endProgress - mCurrentShift.value));
if (mRecentsView != null) {
duration = Math.max(duration, mRecentsView.getScroller().getDuration());
@@ -395,7 +388,7 @@ public class FallbackNoButtonInputConsumer extends
}
};
if (mEndTarget == HOME && !mRunningOverHome) {
if (mGestureState.getEndTarget() == HOME && !mRunningOverHome) {
RectFSpringAnim anim = createWindowAnimationToHome(mCurrentShift.value, duration);
anim.addAnimatorListener(endListener);
anim.start(mEndVelocityPxPerMs);
@@ -404,7 +397,7 @@ public class FallbackNoButtonInputConsumer extends
AnimatorSet anim = new AnimatorSet();
anim.play(mLauncherAlpha.animateToValue(
mLauncherAlpha.value, mEndTarget.mLauncherAlpha));
mLauncherAlpha.value, params.mLauncherAlpha));
anim.play(mCurrentShift.animateToValue(mCurrentShift.value, endProgress));
anim.setDuration(duration);
@@ -429,13 +422,13 @@ public class FallbackNoButtonInputConsumer extends
}
applyTransformUnchecked();
setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
}
@Override
public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
mRecentsView.setRecentsAnimationTargets(null, null);
setStateOnUiThread(STATE_HANDLER_INVALIDATED);
super.onRecentsAnimationCanceled(thumbnailData);
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
/**
@@ -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<La
}
@Override
public void onTransitionCancelled(Launcher activity, boolean activityVisible) {
LauncherState startState = activity.getStateManager().getRestState();
activity.getStateManager().goToState(startState, activityVisible);
public void onTransitionCancelled(boolean activityVisible) {
Launcher launcher = getCreatedActivity();
if (launcher == null) {
return;
}
LauncherState startState = launcher.getStateManager().getRestState();
launcher.getStateManager().goToState(startState, activityVisible);
}
@Override
public void onSwipeUpToRecentsComplete(Launcher activity) {
public void onSwipeUpToRecentsComplete() {
// Re apply state in case we did something funky during the transition.
activity.getStateManager().reapplyState();
DiscoveryBounce.showForOverviewIfNeeded(activity);
Launcher launcher = getCreatedActivity();
if (launcher == null) {
return;
}
launcher.getStateManager().reapplyState();
DiscoveryBounce.showForOverviewIfNeeded(launcher);
}
@Override
public void onSwipeUpToHomeComplete(Launcher activity) {
public void onSwipeUpToHomeComplete() {
Launcher launcher = getCreatedActivity();
if (launcher == null) {
return;
}
// Ensure recents is at the correct position for NORMAL state. For example, when we detach
// recents, we assume the first task is invisible, making translation off by one task.
activity.getStateManager().reapplyState();
launcher.getStateManager().reapplyState();
setLauncherHideBackArrow(false);
}
private void setLauncherHideBackArrow(boolean hideBackArrow) {
Launcher launcher = getCreatedActivity();
if (launcher != null) {
launcher.getRootView().setForceHideBackArrow(hideBackArrow);
if (launcher == null) {
return;
}
launcher.getRootView().setForceHideBackArrow(hideBackArrow);
}
@Override
public void onAssistantVisibilityChanged(float visibility) {
Launcher launcher = getCreatedActivity();
if (launcher != null) {
launcher.onAssistantVisibilityChanged(visibility);
if (launcher == null) {
return;
}
launcher.onAssistantVisibilityChanged(visibility);
}
@NonNull
@Override
public HomeAnimationFactory prepareHomeUI(Launcher activity) {
final DeviceProfile dp = activity.getDeviceProfile();
final RecentsView recentsView = activity.getOverviewPanel();
public HomeAnimationFactory prepareHomeUI() {
Launcher launcher = getCreatedActivity();
final DeviceProfile dp = launcher.getDeviceProfile();
final RecentsView recentsView = launcher.getOverviewPanel();
final TaskView runningTaskView = recentsView.getRunningTaskView();
final View workspaceView;
if (runningTaskView != null && runningTaskView.getTask().key.getComponent() != null) {
workspaceView = activity.getWorkspace().getFirstMatchForAppClose(
workspaceView = launcher.getWorkspace().getFirstMatchForAppClose(
runningTaskView.getTask().key.getComponent().getPackageName(),
UserHandle.of(runningTaskView.getTask().key.userId));
} else {
@@ -144,7 +162,7 @@ public final class LauncherActivityInterface implements BaseActivityInterface<La
final RectF iconLocation = new RectF();
boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow();
FloatingIconView floatingIconView = canUseWorkspaceView
? FloatingIconView.getFloatingIconView(activity, workspaceView,
? FloatingIconView.getFloatingIconView(launcher, workspaceView,
true /* hideOriginal */, iconLocation, false /* isOpening */)
: null;
setLauncherHideBackArrow(true);
@@ -170,14 +188,14 @@ public final class LauncherActivityInterface implements BaseActivityInterface<La
public AnimatorPlaybackController createActivityAnimationToHome() {
// Return an empty APC here since we have an non-user controlled animation to home.
long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
return activity.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy,
return launcher.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy,
0 /* animComponents */);
}
@Override
public void playAtomicAnimation(float velocity) {
// Setup workspace with 0 duration to prepare for our staggered animation.
LauncherStateManager stateManager = activity.getStateManager();
LauncherStateManager stateManager = launcher.getStateManager();
AnimatorSetBuilder builder = new AnimatorSetBuilder();
// setRecentsAttachedToAppWindow() will animate recents out.
builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
@@ -187,39 +205,40 @@ public final class LauncherActivityInterface implements BaseActivityInterface<La
// Stop scrolling so that it doesn't interfere with the translation offscreen.
recentsView.getScroller().forceFinished(true);
new StaggeredWorkspaceAnim(activity, workspaceView, velocity).start();
new StaggeredWorkspaceAnim(launcher, workspaceView, velocity).start();
}
};
}
@Override
public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible,
public AnimationFactory prepareRecentsUI(boolean activityVisible,
boolean animateActivity, Consumer<AnimatorPlaybackController> 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<La
@Override
public void onTransitionCancelled() {
activity.getStateManager().goToState(startState, false /* animate */);
launcher.getStateManager().goToState(startState, false /* animate */);
}
@Override
public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator,
long duration) {
if (mShelfState == shelfState) {
return;
}
mShelfState = shelfState;
activity.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM);
if (mShelfState == ShelfAnimState.CANCEL) {
return;
}
float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(activity);
float shelfOverviewProgress = OVERVIEW.getVerticalProgress(activity);
// Peek based on default overview progress so we can see hotseat if we're showing
// that instead of predictions in overview.
float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(activity);
float shelfPeekingProgress = shelfHiddenProgress
- (shelfHiddenProgress - defaultOverviewProgress) * 0.25f;
float toProgress = mShelfState == ShelfAnimState.HIDE
? shelfHiddenProgress
: mShelfState == ShelfAnimState.PEEK
? shelfPeekingProgress
: shelfOverviewProgress;
Animator shelfAnim = activity.getStateManager()
.createStateElementAnimation(INDEX_SHELF_ANIM, toProgress);
shelfAnim.setInterpolator(interpolator);
shelfAnim.setDuration(duration).start();
mShelfAnim.setShelfState(shelfState, interpolator, duration);
}
@Override
@@ -271,8 +267,8 @@ public final class LauncherActivityInterface implements BaseActivityInterface<La
return;
}
mIsAttachedToWindow = attached;
LauncherRecentsView recentsView = activity.getOverviewPanel();
Animator fadeAnim = activity.getStateManager()
LauncherRecentsView recentsView = launcher.getOverviewPanel();
Animator fadeAnim = launcher.getStateManager()
.createStateElementAnimation(
INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
@@ -286,7 +282,7 @@ public final class LauncherActivityInterface implements BaseActivityInterface<La
float fromTranslationX = attached ? offscreenX - scrollOffsetX : 0;
float toTranslationX = attached ? 0 : offscreenX - scrollOffsetX;
activity.getStateManager()
launcher.getStateManager()
.cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
if (!recentsView.isShown() && animate) {
@@ -298,7 +294,7 @@ public final class LauncherActivityInterface implements BaseActivityInterface<La
if (!animate) {
recentsView.setTranslationX(toTranslationX);
} else {
activity.getStateManager().createStateElementAnimation(
launcher.getStateManager().createStateElementAnimation(
INDEX_RECENTS_TRANSLATE_X_ANIM,
fromTranslationX, toTranslationX).start();
}
@@ -396,15 +392,15 @@ public final class LauncherActivityInterface implements BaseActivityInterface<La
}
@Override
public ActivityInitListener createActivityInitListener(
BiPredicate<Launcher, Boolean> onInitListener) {
return new LauncherInitListenerEx(onInitListener);
public ActivityInitListener createActivityInitListener(Predicate<Boolean> 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<La
}
@Override
public void onLaunchTaskFailed(Launcher launcher) {
public void onLaunchTaskFailed() {
Launcher launcher = getCreatedActivity();
if (launcher == null) {
return;
}
launcher.getStateManager().goToState(OVERVIEW);
}
@Override
public void onLaunchTaskSuccess(Launcher launcher) {
public void onLaunchTaskSuccess() {
Launcher launcher = getCreatedActivity();
if (launcher == null) {
return;
}
launcher.getStateManager().moveToRestState();
}
@@ -493,22 +497,38 @@ public final class LauncherActivityInterface implements BaseActivityInterface<La
}
@Override
public void switchToScreenshot(ThumbnailData thumbnailData, Runnable runnable) {
public void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData,
Runnable onFinishRunnable) {
Launcher launcher = getCreatedActivity();
if (launcher == null) {
return;
}
RecentsView recentsView = launcher.getOverviewPanel();
if (recentsView == null) {
if (runnable != null) {
runnable.run();
if (onFinishRunnable != null) {
onFinishRunnable.run();
}
return;
}
TaskView taskView = recentsView.getRunningTaskView();
if (taskView != null) {
taskView.setShowScreenshot(true);
taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData);
ViewUtils.postDraw(taskView, runnable);
} else if (runnable != null) {
runnable.run();
recentsView.switchToScreenshot(thumbnailData, onFinishRunnable);
}
@Override
public void setOnDeferredActivityLaunchCallback(Runnable r) {
Launcher launcher = getCreatedActivity();
if (launcher == null) {
return;
}
launcher.setOnDeferredActivityLaunchCallback(r);
}
@Override
public void updateOverviewPredictionState() {
Launcher launcher = getCreatedActivity();
if (launcher == null) {
return;
}
PredictionUiStateManager.INSTANCE.get(launcher).switchClient(
PredictionUiStateManager.Client.OVERVIEW);
}
}
@@ -24,13 +24,15 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TI
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState.HIDE;
import static com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState.PEEK;
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.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.HOME;
import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.NEW_TASK;
import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.RECENTS;
import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import android.animation.Animator;
@@ -38,7 +40,6 @@ import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.Intent;
import android.graphics.PointF;
@@ -68,13 +69,15 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.BaseActivityInterface.AnimationFactory;
import com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState;
import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.GestureState.GestureEndTarget;
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AppWindowAnimationHelper.TargetAlphaProvider;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.SharedApiCompat;
import com.android.quickstep.util.ShelfPeekAnim;
import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
import com.android.quickstep.views.LiveTileOverlay;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -83,10 +86,13 @@ import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
/**
* Handles the navigation gestures when Launcher is the default home activity.
*/
@TargetApi(Build.VERSION_CODES.O)
public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
public class LauncherSwipeHandler<T extends BaseDraggingActivity>
extends BaseSwipeUpHandler<T, RecentsView> 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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
*/
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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
// 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<T extends BaseDraggingActivity>
// Launcher is visible, but might be about to stop. Thus, if we prepare recents
// now, it might get overridden by moveToRestState() in onStop(). To avoid this,
// wait until the next gesture (and possibly launcher) starts.
mStateCallback.addCallback(STATE_GESTURE_STARTED, initAnimFactory);
mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, initAnimFactory);
} else {
initAnimFactory.run();
}
@@ -400,15 +376,31 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
// 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<T extends BaseDraggingActivity>
.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<T extends BaseDraggingActivity>
* 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<T extends BaseDraggingActivity>
}
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<T extends BaseDraggingActivity>
@Override
public Intent getLaunchIntent() {
return mOverviewComponentObserver.getOverviewIntent();
return mGestureState.getOverviewIntent();
}
@Override
@@ -555,7 +546,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
}
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<T extends BaseDraggingActivity>
super.onRecentsAnimationStart(controller, targets);
// Only add the callback to enable the input consumer after we actually have the controller
mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
mRecentsAnimationController::enableInputConsumer);
setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
mPassedOverviewThreshold = false;
}
@@ -623,9 +615,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
@Override
public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
super.onRecentsAnimationCanceled(thumbnailData);
mRecentsView.setRecentsAnimationTargets(null, null);
mActivityInitListener.unregister();
setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
}
@@ -633,7 +624,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
public void onGestureStarted() {
notifyGestureStartedAsync();
mShiftAtGestureStart = mCurrentShift.value;
setStateOnUiThread(STATE_GESTURE_STARTED);
mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
mGestureStarted = true;
}
@@ -656,7 +647,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
@Override
public void onGestureCancelled() {
updateDisplacement(0);
setStateOnUiThread(STATE_GESTURE_COMPLETED);
mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
mLogAction = Touch.SWIPE_NOOP;
handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */);
}
@@ -671,7 +662,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
setStateOnUiThread(STATE_GESTURE_COMPLETED);
mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
boolean isVelocityVertical = Math.abs(velocity.y) > Math.abs(velocity.x);
@@ -686,11 +677,11 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
@Override
protected InputConsumer createNewInputProxyHandler() {
endRunningWindowAnim(mGestureEndTarget == HOME /* cancel */);
endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
endLauncherTransitionController();
if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
// Hide the task view, if not already hidden
setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
setTargetAlphaProvider(LauncherSwipeHandler::getHiddenTargetAlpha);
}
BaseDraggingActivity activity = mActivityInterface.getCreatedActivity();
@@ -708,6 +699,24 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
}
}
private void onEndTargetSet() {
switch (mGestureState.getEndTarget()) {
case HOME:
mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
break;
case RECENTS:
mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
| STATE_SCREENSHOT_VIEW_SHOWN);
break;
case NEW_TASK:
mStateCallback.setState(STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT);
break;
case LAST_TASK:
mStateCallback.setState(STATE_RESUME_LAST_TASK);
break;
}
}
private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling,
boolean isCancel) {
final GestureEndTarget endTarget;
@@ -729,7 +738,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
if (!isFling) {
if (isCancel) {
endTarget = LAST_TASK;
} else if (mMode == Mode.NO_BUTTON) {
} else if (mDeviceState.isFullyGesturalNavMode()) {
if (mIsShelfPeeking) {
endTarget = RECENTS;
} else if (goingToNewTask) {
@@ -750,9 +759,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
boolean willGoToNewTaskOnSwipeUp =
goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity);
if (mMode == Mode.NO_BUTTON && isSwipeUp && !willGoToNewTaskOnSwipeUp) {
if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !willGoToNewTaskOnSwipeUp) {
endTarget = HOME;
} else if (mMode == Mode.NO_BUTTON && isSwipeUp && !mIsShelfPeeking) {
} else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !mIsShelfPeeking) {
// If swiping at a diagonal, base end target on the faster velocity.
endTarget = NEW_TASK;
} else if (isSwipeUp) {
@@ -777,7 +786,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
float currentShift = mCurrentShift.value;
final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity,
isFling, isCancel);
float endShift = endTarget.endShift;
float endShift = endTarget.isLauncher ? 1 : 0;
final float startShift;
Interpolator interpolator = DEACCEL;
if (!isFling) {
@@ -792,7 +801,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
float minFlingVelocity = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_min_velocity);
if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
if (endTarget == RECENTS && mMode != Mode.NO_BUTTON) {
if (endTarget == RECENTS && !mDeviceState.isFullyGesturalNavMode()) {
Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
startShift, endShift, endShift, endVelocity / 1000,
mTransitionDragLength, mContext);
@@ -825,7 +834,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
} else if (endTarget == RECENTS) {
mLiveTileOverlay.startIconAnimation();
LiveTileOverlay.getInstance().startIconAnimation();
if (mRecentsView != null) {
int nearestPage = mRecentsView.getPageNearestToCenterOfScreen();
if (mRecentsView.getNextPage() != nearestPage) {
@@ -838,7 +847,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
}
duration = Math.max(duration, mRecentsView.getScroller().getDuration());
}
if (mMode == Mode.NO_BUTTON) {
if (mDeviceState.isFullyGesturalNavMode()) {
setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration);
}
} else if (endTarget == NEW_TASK || endTarget == LAST_TASK) {
@@ -880,14 +889,15 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
@UiThread
private void animateToProgressInternal(float start, float end, long duration,
Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
mGestureEndTarget = target;
// Set the state, but don't notify until the animation completes
mGestureState.setEndTarget(target, false /* isAtomic */);
maybeUpdateRecentsAttachedState();
if (mGestureEndTarget == HOME) {
if (mGestureState.getEndTarget() == HOME) {
HomeAnimationFactory homeAnimFactory;
if (mActivity != null) {
homeAnimFactory = mActivityInterface.prepareHomeUI(mActivity);
homeAnimFactory = mActivityInterface.prepareHomeUI();
} else {
homeAnimFactory = new HomeAnimationFactory() {
@NonNull
@@ -904,14 +914,15 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
}
};
mStateCallback.addChangeHandler(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
isPresent -> mRecentsView.startHome());
}
RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
windowAnim.addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
setStateOnUiThread(target.endState);
// Finalize the state and notify of the change
mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
}
});
windowAnim.start(velocityPxPerMs);
@@ -936,10 +947,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
// We are about to launch the current running task, so use LAST_TASK state
// instead of NEW_TASK. This could happen, for example, if our scroll is
// aborted after we determined the target to be NEW_TASK.
setStateOnUiThread(LAST_TASK.endState);
} else {
setStateOnUiThread(target.endState);
mGestureState.setEndTarget(LAST_TASK);
}
mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
}
});
windowAnim.start();
@@ -947,7 +957,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
}
// Always play the entire launcher animation when going home, since it is separate from
// the animation that has been controlled thus far.
if (mGestureEndTarget == HOME) {
if (mGestureState.getEndTarget() == HOME) {
start = 0;
}
@@ -999,21 +1009,22 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
}
// Make sure recents is in its final state
maybeUpdateRecentsAttachedState(false);
mActivityInterface.onSwipeUpToHomeComplete(mActivity);
mActivityInterface.onSwipeUpToHomeComplete();
}
});
return anim;
}
@Override
public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) {
if (mGestureEndTarget != null) {
sharedState.canGestureBeContinued = mGestureEndTarget.canBeContinued;
sharedState.goingToLauncher = mGestureEndTarget.isLauncher;
public void onConsumerAboutToBeSwitched() {
if (mActivity != null) {
// In the off chance that the gesture ends before Launcher is started, we should clear
// the callback here so that it doesn't update with the wrong state
mActivity.clearRunOnceOnStartCallback();
resetLauncherListenersAndOverlays();
}
if (sharedState.canGestureBeContinued) {
cancelCurrentAnimation(sharedState);
if (mGestureState.getEndTarget() != null && !mGestureState.isRunningAnimationToLauncher()) {
cancelCurrentAnimation();
} else {
reset();
}
@@ -1033,6 +1044,15 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
@UiThread
private void startNewTask() {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
mRecentsAnimationController.finish(true /* toRecents */, this::startNewTaskInternal);
} else {
startNewTaskInternal();
}
}
@UiThread
private void startNewTaskInternal() {
startNewTask(STATE_HANDLER_INVALIDATED, success -> {
if (!success) {
// We couldn't launch the task, so take user to overview so they can
@@ -1045,14 +1065,14 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
}
private void reset() {
setStateOnUiThread(STATE_HANDLER_INVALIDATED);
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
/**
* Cancels any running animation so that the active target can be overriden by a new swipe
* handle (in case of quick switch).
*/
private void cancelCurrentAnimation(SwipeSharedState sharedState) {
private void cancelCurrentAnimation() {
mCanceled = true;
mCurrentShift.cancelAnimation();
if (mLauncherTransitionController != null && mLauncherTransitionController
@@ -1070,7 +1090,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
? newRunningTaskView.getTask().key.id
: -1;
mRecentsView.setCurrentTask(newRunningTaskId);
sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId);
mGestureState.setFinishingRecentsAnimationTaskId(newRunningTaskId);
}
}
@@ -1089,9 +1109,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
endLauncherTransitionController();
mRecentsView.onGestureAnimationEnd();
mActivity.getRootView().setOnApplyWindowInsetsListener(null);
removeLiveTileOverlay();
resetLauncherListenersAndOverlays();
}
private void endLauncherTransitionController() {
@@ -1102,58 +1120,71 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
}
}
private void resetLauncherListenersAndOverlays() {
// Reset the callback for deferred activity launches
if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
mActivityInterface.setOnDeferredActivityLaunchCallback(null);
}
mActivity.getRootView().setOnApplyWindowInsetsListener(null);
removeLiveTileOverlay();
}
private void notifyTransitionCancelled() {
mAnimationFactory.onTransitionCancelled();
}
private void resetStateForAnimationCancel() {
boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
mActivityInterface.onTransitionCancelled(mActivity, wasVisible);
mActivityInterface.onTransitionCancelled(wasVisible);
// Leave the pending invisible flag, as it may be used by wallpaper open animation.
mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
}
private void switchToScreenshot() {
final int runningTaskId = mGestureState.getRunningTaskId();
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (mRecentsAnimationController != null) {
SharedApiCompat.setWillFinishToHome(mRecentsAnimationController.getController(),
true /* willFinishToHome */);
// Update the screenshot of the task
if (mTaskSnapshot == null) {
mTaskSnapshot = mRecentsAnimationController.screenshotTask(mRunningTaskId);
mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId);
}
mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot, false /* refreshNow */);
mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot, false /* refreshNow */);
}
setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
} else if (!hasTargets()) {
// If there are no targets, then we don't need to capture anything
setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
} else {
boolean finishTransitionPosted = false;
if (mRecentsAnimationController != null) {
// Update the screenshot of the task
if (mTaskSnapshot == null) {
mTaskSnapshot = mRecentsAnimationController.screenshotTask(mRunningTaskId);
mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId);
}
final TaskView taskView;
if (mGestureEndTarget == HOME) {
if (mGestureState.getEndTarget() == HOME) {
// Capture the screenshot before finishing the transition to home to ensure it's
// taken in the correct orientation, but no need to update the thumbnail.
taskView = null;
} else {
taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot);
taskView = mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot);
}
if (taskView != null && !mCanceled) {
// Defer finishing the animation until the next launcher frame with the
// new thumbnail
finishTransitionPosted = ViewUtils.postDraw(taskView,
() -> setStateOnUiThread(STATE_SCREENSHOT_CAPTURED), this::isCanceled);
() -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
this::isCanceled);
}
}
if (!finishTransitionPosted) {
// If we haven't posted a draw callback, set the state immediately.
Object traceToken = TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT,
TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS);
setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
TraceHelper.INSTANCE.endSection(traceToken);
}
}
@@ -1161,23 +1192,24 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
private void finishCurrentTransitionToRecents() {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
} else if (!hasTargets()) {
// If there are no targets, then there is nothing to finish
setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
} else if (!hasTargets() || mRecentsAnimationController == null) {
// If there are no targets or the animation not started, then there is nothing to finish
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
} else {
synchronized (mRecentsAnimationController) {
mRecentsAnimationController.finish(true /* toRecents */,
() -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
}
mRecentsAnimationController.finish(true /* toRecents */,
() -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
}
ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
}
private void finishCurrentTransitionToHome() {
synchronized (mRecentsAnimationController) {
if (!hasTargets() || mRecentsAnimationController == null) {
// If there are no targets or the animation not started, then there is nothing to finish
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
} else {
mRecentsAnimationController.finish(true /* toRecents */,
() -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED),
() -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED),
true /* sendUserLeaveHint */);
}
ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
@@ -1186,15 +1218,14 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
endLauncherTransitionController();
mActivityInterface.onSwipeUpToRecentsComplete(mActivity);
mActivityInterface.onSwipeUpToRecentsComplete();
if (mRecentsAnimationController != null) {
mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
true /* screenshot */);
}
mRecentsView.onSwipeUpAnimationSuccess();
RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
doLogGesture(RECENTS);
reset();
}
@@ -1204,20 +1235,15 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
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) {
@@ -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);
}
@@ -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();
}
}
@@ -89,9 +89,13 @@ public final class RecentsActivity extends BaseRecentsActivity {
int taskID = intent.getIntExtra(EXTRA_TASK_ID, 0);
IBinder thumbnail = intent.getExtras().getBinder(EXTRA_THUMBNAIL);
if (taskID != 0 && thumbnail instanceof ObjectWrapper) {
ThumbnailData thumbnailData = ((ObjectWrapper<ThumbnailData>) thumbnail).get();
ObjectWrapper<ThumbnailData> obj = (ObjectWrapper<ThumbnailData>) thumbnail;
ThumbnailData thumbnailData = obj.get();
mFallbackRecentsView.showCurrentTask(taskID);
mFallbackRecentsView.updateThumbnail(taskID, thumbnailData);
// Clear the ref since any reference to the extras on the system side will still
// hold a reference to the wrapper
obj.clear();
}
}
intent.removeExtra(EXTRA_TASK_ID);
@@ -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;
}
}
@@ -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<TaskOverlayFactory> INSTANCE =
forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class);
public List<TaskSystemShortcut> getEnabledShortcuts(TaskView taskView) {
final ArrayList<TaskSystemShortcut> shortcuts = new ArrayList<>();
public static List<SystemShortcut> getEnabledShortcuts(TaskView taskView) {
final ArrayList<SystemShortcut> 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<TaskOverlayFactory> INSTANCE =
forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class);
public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) {
return new TaskOverlay();
}
@@ -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<AppTransitionAnimationSpecCompat> 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<Boolean> 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));
}
@@ -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<T extends SystemShortcut> 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<SystemShortcut.AppInfo> {
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<AppTransitionAnimationSpecCompat> 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<Boolean> 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<SystemShortcut.Install> {
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;
}
}
}
@@ -172,8 +172,11 @@ public final class TaskViewUtils {
AppWindowAnimationHelper.TransformParams liveTileParams =
v.getRecentsView().getLiveTileParams(true /* mightNeedToRefill */);
if (liveTileParams != null) {
Collections.addAll(surfaceParamsList,
liveTileAnimationHelper.getSurfaceParams(liveTileParams));
SurfaceParams[] liveTileSurfaceParams =
liveTileAnimationHelper.getSurfaceParams(liveTileParams);
if (liveTileSurfaceParams != null) {
Collections.addAll(surfaceParamsList, liveTileSurfaceParams);
}
}
}
// Apply surface transform using the surface params list.
@@ -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<String> {
* 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<OverscrollPlugin> {
private static final String TAG = "TouchInteractionService";
@@ -122,6 +120,8 @@ public class TouchInteractionService extends Service implements
private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
private static final int MAX_BACK_NOTIFICATION_COUNT = 3;
private int mBackGestureNotificationCounter = -1;
@Nullable
private OverscrollPlugin mOverscrollPlugin;
private final IBinder mMyBinder = new IOverviewProxy.Stub() {
@@ -129,13 +129,11 @@ public class TouchInteractionService extends Service implements
public void onInitialize(Bundle bundle) {
ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
MAIN_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(TouchInteractionService.this)
.setProxy(proxy));
MAIN_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
MAIN_EXECUTOR.execute(() -> preloadOverview(true /* fromInit */));
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "TIS initialized");
}
MAIN_EXECUTOR.execute(() -> {
SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy);
TouchInteractionService.this.initInputMonitor();
preloadOverview(true /* fromInit */);
});
sIsInitialized = true;
}
@@ -169,15 +167,19 @@ public class TouchInteractionService extends Service implements
@BinderThread
@Override
public void onAssistantAvailable(boolean available) {
MAIN_EXECUTOR.execute(() -> mDeviceState.setAssistantAvailable(available));
MAIN_EXECUTOR.execute(TouchInteractionService.this::onAssistantVisibilityChanged);
MAIN_EXECUTOR.execute(() -> {
mDeviceState.setAssistantAvailable(available);
TouchInteractionService.this.onAssistantVisibilityChanged();
});
}
@BinderThread
@Override
public void onAssistantVisibilityChanged(float visibility) {
MAIN_EXECUTOR.execute(() -> mDeviceState.setAssistantVisibility(visibility));
MAIN_EXECUTOR.execute(TouchInteractionService.this::onAssistantVisibilityChanged);
MAIN_EXECUTOR.execute(() -> {
mDeviceState.setAssistantVisibility(visibility);
TouchInteractionService.this.onAssistantVisibilityChanged();
});
}
@BinderThread
@@ -199,8 +201,10 @@ public class TouchInteractionService extends Service implements
@BinderThread
public void onSystemUiStateChanged(int stateFlags) {
MAIN_EXECUTOR.execute(() -> mDeviceState.setSystemUiFlags(stateFlags));
MAIN_EXECUTOR.execute(TouchInteractionService.this::onSystemUiFlagsChanged);
MAIN_EXECUTOR.execute(() -> {
mDeviceState.setSystemUiFlags(stateFlags);
TouchInteractionService.this.onSystemUiFlagsChanged();
});
}
@BinderThread
@@ -228,8 +232,6 @@ public class TouchInteractionService extends Service implements
private static boolean sConnected = false;
private static boolean sIsInitialized = false;
private static final SwipeSharedState sSwipeSharedState = new SwipeSharedState();
private int mLogId;
public static boolean isConnected() {
return sConnected;
@@ -239,45 +241,38 @@ public class TouchInteractionService extends Service implements
return sIsInitialized;
}
public static SwipeSharedState getSwipeSharedState() {
return sSwipeSharedState;
}
private final InputConsumer mResetGestureInputConsumer =
new ResetGestureInputConsumer(sSwipeSharedState);
private final BaseSwipeUpHandler.Factory mWindowTreansformFactory =
this::createWindowTransformSwipeHandler;
private final BaseSwipeUpHandler.Factory mFallbackNoButtonFactory =
this::createFallbackNoButtonSwipeHandler;
private final BaseSwipeUpHandler.Factory mLauncherSwipeHandlerFactory =
this::createLauncherSwipeHandler;
private final BaseSwipeUpHandler.Factory mFallbackSwipeHandlerFactory =
this::createFallbackSwipeHandler;
private ActivityManagerWrapper mAM;
private RecentsModel mRecentsModel;
private OverviewCommandHelper mOverviewCommandHelper;
private OverviewComponentObserver mOverviewComponentObserver;
private InputConsumerController mInputConsumer;
private RecentsAnimationDeviceState mDeviceState;
private TaskAnimationManager mTaskAnimationManager;
private InputConsumer mUncheckedConsumer = InputConsumer.NO_OP;
private InputConsumer mConsumer = InputConsumer.NO_OP;
private Choreographer mMainChoreographer;
private InputConsumer mResetGestureInputConsumer;
private GestureState mGestureState = new GestureState();
private InputMonitorCompat mInputMonitorCompat;
private InputEventReceiver mInputEventReceiver;
private Mode mMode = Mode.THREE_BUTTONS;
@Override
public void onCreate() {
super.onCreate();
mDeviceState = new RecentsAnimationDeviceState(this);
mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
// Initialize anything here that is needed in direct boot mode.
// Everything else should be initialized in onUserUnlocked() below.
mMainChoreographer = Choreographer.getInstance();
mAM = ActivityManagerWrapper.getInstance();
mDeviceState = new RecentsAnimationDeviceState(this);
mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this));
sConnected = true;
}
@@ -300,7 +295,7 @@ public class TouchInteractionService extends Service implements
Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 1");
}
disposeEventHandlers();
if (!mMode.hasGestures || !SystemUiProxy.INSTANCE.get(this).isActive()) {
if (mDeviceState.isButtonNavMode() || !SystemUiProxy.INSTANCE.get(this).isActive()) {
return;
}
if (TestProtocol.sDebugTracing) {
@@ -319,25 +314,22 @@ public class TouchInteractionService extends Service implements
mDeviceState.updateGestureTouchRegions();
}
@Override
public void onNavigationModeChanged(Mode newMode) {
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "onNavigationModeChanged " + newMode);
}
mMode = newMode;
/**
* Called when the navigation mode changes, guaranteed to be after the device state has updated.
*/
private void onNavigationModeChanged(SysUINavigationMode.Mode mode) {
initInputMonitor();
resetHomeBounceSeenOnQuickstepEnabledFirstTime();
}
@UiThread
public void onUserUnlocked() {
mRecentsModel = RecentsModel.INSTANCE.get(this);
mTaskAnimationManager = new TaskAnimationManager();
mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
mOverviewCommandHelper = new OverviewCommandHelper(this, mDeviceState,
mOverviewComponentObserver);
mResetGestureInputConsumer = new ResetGestureInputConsumer(mTaskAnimationManager);
mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
sSwipeSharedState.setOverviewComponentObserver(mOverviewComponentObserver);
mInputConsumer.registerInputConsumer();
onSystemUiFlagsChanged();
onAssistantVisibilityChanged();
@@ -347,10 +339,24 @@ public class TouchInteractionService extends Service implements
mBackGestureNotificationCounter = Math.max(0, Utilities.getDevicePrefs(this)
.getInt(KEY_BACK_NOTIFICATION_COUNT, MAX_BACK_NOTIFICATION_COUNT));
resetHomeBounceSeenOnQuickstepEnabledFirstTime();
PluginManagerWrapper.INSTANCE.get(getBaseContext()).addPluginListener(this,
OverscrollPlugin.class, false /* allowMultiple */);
}
private void onDeferredActivityLaunch() {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
mOverviewComponentObserver.getActivityInterface().switchRunningTaskViewToScreenshot(
null, () -> {
mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
});
} else {
mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
}
}
private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
if (!mDeviceState.isUserUnlocked() || !mMode.hasGestures) {
if (!mDeviceState.isUserUnlocked() || mDeviceState.isButtonNavMode()) {
// Skip if not yet unlocked (can't read user shared prefs) or if the current navigation
// mode doesn't have gestures
return;
@@ -385,9 +391,8 @@ public class TouchInteractionService extends Service implements
@Override
public void onDestroy() {
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "TIS destroyed");
}
PluginManagerWrapper.INSTANCE.get(getBaseContext()).removePluginListener(this);
sIsInitialized = false;
if (mDeviceState.isUserUnlocked()) {
mInputConsumer.unregisterInputConsumer();
@@ -395,7 +400,6 @@ public class TouchInteractionService extends Service implements
}
disposeEventHandlers();
mDeviceState.destroy();
SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
SystemUiProxy.INSTANCE.get(this).setProxy(null);
sConnected = false;
@@ -424,19 +428,19 @@ public class TouchInteractionService extends Service implements
TraceHelper.FLAG_ALLOW_BINDER_TRACKING);
MotionEvent event = (MotionEvent) ev;
if (event.getAction() == ACTION_DOWN) {
GestureState newGestureState = new GestureState(
mOverviewComponentObserver.getActivityInterface());
mLogId = ActiveGestureLog.INSTANCE.generateAndSetLogId();
sSwipeSharedState.setLogTraceId(mLogId);
GestureState newGestureState = new GestureState(mOverviewComponentObserver,
ActiveGestureLog.INSTANCE.generateAndSetLogId());
newGestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0",
() -> mAM.getRunningTask(0)));
if (mDeviceState.isInSwipeUpTouchRegion(event)) {
boolean useSharedState = mConsumer.useSharedSwipeState();
mConsumer.onConsumerAboutToBeSwitched();
mConsumer = newConsumer(newGestureState, useSharedState, event);
mConsumer = newConsumer(mGestureState, newGestureState, event);
ActiveGestureLog.INSTANCE.addLog("setInputConsumer", mConsumer.getType());
mUncheckedConsumer = mConsumer;
} else if (mDeviceState.isUserUnlocked() && mMode == Mode.NO_BUTTON
} else if (mDeviceState.isUserUnlocked()
&& mDeviceState.isFullyGesturalNavMode()
&& mDeviceState.canTriggerAssistantAction(event)) {
// Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we should
// not interrupt it. QuickSwitch assumes that interruption can only happen if the
@@ -446,6 +450,9 @@ public class TouchInteractionService extends Service implements
} else {
mUncheckedConsumer = InputConsumer.NO_OP;
}
// Save the current gesture state
mGestureState = newGestureState;
}
ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
@@ -453,39 +460,41 @@ public class TouchInteractionService extends Service implements
TraceHelper.INSTANCE.endFlagsOverride(traceToken);
}
private InputConsumer newConsumer(GestureState gestureState, boolean useSharedState,
MotionEvent event) {
private InputConsumer newConsumer(GestureState previousGestureState,
GestureState newGestureState, MotionEvent event) {
boolean canStartSystemGesture = mDeviceState.canStartSystemGesture();
if (!mDeviceState.isUserUnlocked()) {
if (canStartSystemGesture) {
// This handles apps launched in direct boot mode (e.g. dialer) as well as apps
// launched while device is locked even after exiting direct boot mode (e.g. camera).
return createDeviceLockedInputConsumer(gestureState,
mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT));
return createDeviceLockedInputConsumer(newGestureState);
} else {
return mResetGestureInputConsumer;
}
}
// When using sharedState, bypass systemState check as this is a followup gesture and the
// first gesture started in a valid system state.
InputConsumer base = canStartSystemGesture || useSharedState
? newBaseConsumer(gestureState, useSharedState, event) : mResetGestureInputConsumer;
if (mMode == Mode.NO_BUTTON) {
// When there is an existing recents animation running, bypass systemState check as this is
// a followup gesture and the first gesture started in a valid system state.
InputConsumer base = canStartSystemGesture
|| previousGestureState.isRecentsAnimationRunning()
? newBaseConsumer(previousGestureState, newGestureState, event)
: mResetGestureInputConsumer;
if (mDeviceState.isFullyGesturalNavMode()) {
if (mDeviceState.canTriggerAssistantAction(event)) {
base = new AssistantInputConsumer(this, gestureState, base, mInputMonitorCompat);
base = new AssistantInputConsumer(this, newGestureState, base, mInputMonitorCompat);
}
if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()) {
// Put the Compose gesture as higher priority than the Assistant or base gestures
base = new QuickCaptureInputConsumer(this, gestureState, base, mInputMonitorCompat);
if (mOverscrollPlugin != null) {
// Put the overscroll gesture as higher priority than the Assistant or base gestures
base = new OverscrollInputConsumer(this, newGestureState, base, mInputMonitorCompat,
mOverscrollPlugin);
}
if (mDeviceState.isScreenPinningActive()) {
// Note: we only allow accessibility to wrap this, and it replaces the previous
// base input consumer (which should be NO_OP anyway since topTaskLocked == true).
base = new ScreenPinnedInputConsumer(this, gestureState);
base = new ScreenPinnedInputConsumer(this, newGestureState);
}
if (mDeviceState.isAccessibilityMenuAvailable()) {
@@ -500,105 +509,99 @@ public class TouchInteractionService extends Service implements
return base;
}
private InputConsumer newBaseConsumer(GestureState gestureState, boolean useSharedState,
MotionEvent event) {
RunningTaskInfo runningTaskInfo = TraceHelper.whitelistIpcs("getRunningTask.0",
() -> mAM.getRunningTask(0));
if (!useSharedState) {
sSwipeSharedState.clearAllState(false /* finishAnimation */);
}
private InputConsumer newBaseConsumer(GestureState previousGestureState,
GestureState gestureState, MotionEvent event) {
if (mDeviceState.isKeyguardShowingOccluded()) {
// This handles apps showing over the lockscreen (e.g. camera)
return createDeviceLockedInputConsumer(gestureState, runningTaskInfo);
return createDeviceLockedInputConsumer(gestureState);
}
boolean forceOverviewInputConsumer = false;
if (isExcludedAssistant(runningTaskInfo)) {
if (AssistantUtilities.isExcludedAssistant(gestureState.getRunningTask())) {
// In the case where we are in the excluded assistant state, ignore it and treat the
// running activity as the task behind the assistant
runningTaskInfo = TraceHelper.whitelistIpcs("getRunningTask.assistant",
() -> mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT));
if (!ActivityManagerWrapper.isHomeTask(runningTaskInfo)) {
final ComponentName homeComponent =
mOverviewComponentObserver.getHomeIntent().getComponent();
forceOverviewInputConsumer =
runningTaskInfo.baseIntent.getComponent(). equals(homeComponent);
}
gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.assistant",
() -> mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT /* ignoreActivityType */)));
ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent();
ComponentName runningComponent =
gestureState.getRunningTask().baseIntent.getComponent();
forceOverviewInputConsumer =
runningComponent != null && runningComponent.equals(homeComponent);
}
if (runningTaskInfo == null && !sSwipeSharedState.goingToLauncher
&& !sSwipeSharedState.recentsAnimationFinishInterrupted) {
return mResetGestureInputConsumer;
} else if (sSwipeSharedState.recentsAnimationFinishInterrupted) {
if (previousGestureState.getFinishingRecentsAnimationTaskId() > 0) {
// If the finish animation was interrupted, then continue using the other activity input
// consumer but with the next task as the running task
RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
info.id = sSwipeSharedState.nextRunningTaskId;
return createOtherActivityInputConsumer(gestureState, event, info);
} else if (sSwipeSharedState.goingToLauncher
info.id = previousGestureState.getFinishingRecentsAnimationTaskId();
gestureState.updateRunningTask(info);
return createOtherActivityInputConsumer(previousGestureState, gestureState, event);
} else if (gestureState.getRunningTask() == null) {
return mResetGestureInputConsumer;
} else if (previousGestureState.isRunningAnimationToLauncher()
|| gestureState.getActivityInterface().isResumed()
|| forceOverviewInputConsumer) {
return createOverviewInputConsumer(gestureState, event);
return createOverviewInputConsumer(
previousGestureState, gestureState, event, forceOverviewInputConsumer);
} else if (ENABLE_QUICKSTEP_LIVE_TILE.get()
&& gestureState.getActivityInterface().isInLiveTileMode()) {
return createOverviewInputConsumer(gestureState, event);
} else if (mDeviceState.isGestureBlockedActivity(runningTaskInfo)) {
return createOverviewInputConsumer(
previousGestureState, gestureState, event, forceOverviewInputConsumer);
} else if (mDeviceState.isGestureBlockedActivity(gestureState.getRunningTask())) {
return mResetGestureInputConsumer;
} else {
return createOtherActivityInputConsumer(gestureState, event, runningTaskInfo);
return createOtherActivityInputConsumer(previousGestureState, gestureState, event);
}
}
private boolean isExcludedAssistant(TaskInfo info) {
return info != null
&& TaskInfoCompat.getActivityType(info) == ACTIVITY_TYPE_ASSISTANT
&& (info.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
}
private InputConsumer createOtherActivityInputConsumer(GestureState gestureState,
MotionEvent event, RunningTaskInfo runningTaskInfo) {
private InputConsumer createOtherActivityInputConsumer(GestureState previousGestureState,
GestureState gestureState, MotionEvent event) {
final boolean shouldDefer;
final BaseSwipeUpHandler.Factory factory;
if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
shouldDefer = !sSwipeSharedState.recentsAnimationFinishInterrupted;
factory = mFallbackNoButtonFactory;
if (mDeviceState.isFullyGesturalNavMode()
&& !mOverviewComponentObserver.isHomeAndOverviewSame()) {
shouldDefer = previousGestureState.getFinishingRecentsAnimationTaskId() < 0;
factory = mFallbackSwipeHandlerFactory;
} else {
shouldDefer = gestureState.getActivityInterface().deferStartingActivity(mDeviceState,
event);
factory = mWindowTreansformFactory;
factory = mLauncherSwipeHandlerFactory;
}
final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
return new OtherActivityInputConsumer(this, mDeviceState, gestureState, runningTaskInfo,
shouldDefer, this::onConsumerInactive, sSwipeSharedState, mInputMonitorCompat,
disableHorizontalSwipe, factory, mLogId);
return new OtherActivityInputConsumer(this, mDeviceState, mTaskAnimationManager,
gestureState, shouldDefer, this::onConsumerInactive,
mInputMonitorCompat, disableHorizontalSwipe, factory);
}
private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState,
RunningTaskInfo taskInfo) {
if (mMode == Mode.NO_BUTTON && taskInfo != null) {
return new DeviceLockedInputConsumer(this, mDeviceState, gestureState,
sSwipeSharedState, mInputMonitorCompat, taskInfo.taskId, mLogId);
private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState) {
if (mDeviceState.isFullyGesturalNavMode() && gestureState.getRunningTask() != null) {
return new DeviceLockedInputConsumer(this, mDeviceState, mTaskAnimationManager,
gestureState, mInputMonitorCompat);
} else {
return mResetGestureInputConsumer;
}
}
public InputConsumer createOverviewInputConsumer(GestureState gestureState, MotionEvent event) {
public InputConsumer createOverviewInputConsumer(GestureState previousGestureState,
GestureState gestureState, MotionEvent event,
boolean forceOverviewInputConsumer) {
BaseDraggingActivity activity = gestureState.getActivityInterface().getCreatedActivity();
if (activity == null) {
return mResetGestureInputConsumer;
}
if (activity.getRootView().hasWindowFocus() || sSwipeSharedState.goingToLauncher) {
if (activity.getRootView().hasWindowFocus()
|| previousGestureState.isRunningAnimationToLauncher()
|| (FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
&& forceOverviewInputConsumer)) {
return new OverviewInputConsumer(gestureState, activity, mInputMonitorCompat,
false /* startingInActivityBounds */);
} else {
final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
return new OverviewWithoutFocusInputConsumer(activity, gestureState,
return new OverviewWithoutFocusInputConsumer(activity, mDeviceState, gestureState,
mInputMonitorCompat, disableHorizontalSwipe);
}
}
@@ -617,7 +620,7 @@ public class TouchInteractionService extends Service implements
if (!mDeviceState.isUserUnlocked()) {
return;
}
if (!mMode.hasGestures && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
if (mDeviceState.isButtonNavMode() && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
// Prevent the overview from being started before the real home on first boot.
return;
}
@@ -632,8 +635,8 @@ public class TouchInteractionService extends Service implements
mOverviewComponentObserver.getActivityInterface();
if (activityInterface.getCreatedActivity() == null) {
// Make sure that UI states will be initialized.
activityInterface.createActivityInitListener((activity, wasVisible) -> {
AppLaunchTracker.INSTANCE.get(activity);
activityInterface.createActivityInitListener((wasVisible) -> {
AppLaunchTracker.INSTANCE.get(TouchInteractionService.this);
return false;
}).register();
} else if (fromInit) {
@@ -643,9 +646,8 @@ public class TouchInteractionService extends Service implements
return;
}
// Pass null animation handler to indicate this start is preload.
startRecentsActivityAsync(mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState(),
null);
mTaskAnimationManager.preloadRecentsAnimation(
mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState());
}
@Override
@@ -685,14 +687,9 @@ public class TouchInteractionService extends Service implements
// Dump everything
mDeviceState.dump(pw);
pw.println("TouchState:");
pw.println(" navMode=" + mMode);
boolean resumed = mOverviewComponentObserver != null
&& mOverviewComponentObserver.getActivityInterface().isResumed();
pw.println(" resumed=" + resumed);
pw.println(" useSharedState=" + mConsumer.useSharedSwipeState());
if (mConsumer.useSharedSwipeState()) {
sSwipeSharedState.dump(" ", pw);
}
pw.println(" mConsumer=" + mConsumer.getName());
pw.println("FeatureFlags:");
pw.println(" APPLY_CONFIG_AT_RUNTIME=" + APPLY_CONFIG_AT_RUNTIME.get());
@@ -718,20 +715,16 @@ public class TouchInteractionService extends Service implements
}
}
private BaseSwipeUpHandler createWindowTransformSwipeHandler(GestureState gestureState,
RunningTaskInfo runningTask, long touchTimeMs, boolean continuingLastGesture,
boolean isLikelyToStartNewTask) {
return new WindowTransformSwipeHandler(this, mDeviceState, gestureState, runningTask,
touchTimeMs, mOverviewComponentObserver, continuingLastGesture, mInputConsumer,
mRecentsModel);
private BaseSwipeUpHandler createLauncherSwipeHandler(GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
return new LauncherSwipeHandler(this, mDeviceState, mTaskAnimationManager,
gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
}
private BaseSwipeUpHandler createFallbackNoButtonSwipeHandler(GestureState gestureState,
RunningTaskInfo runningTask, long touchTimeMs, boolean continuingLastGesture,
boolean isLikelyToStartNewTask) {
return new FallbackNoButtonInputConsumer(this, gestureState, mOverviewComponentObserver,
runningTask, mRecentsModel, mInputConsumer, isLikelyToStartNewTask,
continuingLastGesture);
private BaseSwipeUpHandler createFallbackSwipeHandler(GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
return new FallbackSwipeHandler(this, mDeviceState, gestureState,
mInputConsumer, isLikelyToStartNewTask, continuingLastGesture);
}
protected boolean shouldNotifyBackGesture() {
@@ -754,4 +747,14 @@ public class TouchInteractionService extends Service implements
UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
.startRecentsActivity(intent, null, listener, null, null));
}
@Override
public void onPluginConnected(OverscrollPlugin overscrollPlugin, Context context) {
mOverscrollPlugin = overscrollPlugin;
}
@Override
public void onPluginDisconnected(OverscrollPlugin overscrollPlugin) {
mOverscrollPlugin = null;
}
}
@@ -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;
@@ -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);
}
@@ -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<OtherActivityInputConsumer> 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<OtherActivityInputConsumer> 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;
@@ -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 <T> Draggable activity subclass used by RecentsView
*/
public class QuickCaptureInputConsumer<T extends BaseDraggingActivity>
extends DelegateInputConsumer {
public class OverscrollInputConsumer<T extends BaseDraggingActivity> 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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
@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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
}
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<T extends BaseDraggingActivity>
}
}
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<T extends BaseDraggingActivity>
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;
}
}
@@ -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.
@@ -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 */);
}
}
}
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.logging;
import android.content.Context;
import androidx.annotation.NonNull;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.userevent.nano.LauncherLogProto;
/**
* This class handles AOSP MetricsLogger function calls and logging around
* quickstep interactions and app launches.
*/
@SuppressWarnings("unused")
public class UserEventDispatcherAppPredictionExtension extends UserEventDispatcherExtension {
public static final int ALL_APPS_PREDICTION_TIPS = 2;
private static final String TAG = "UserEventDispatcher";
public UserEventDispatcherAppPredictionExtension(Context context) {
super(context);
}
@Override
protected void onFillInLogContainerData(
@NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target,
@NonNull LauncherLogProto.Target targetParent) {
PredictionUiStateManager.fillInPredictedRank(itemInfo, target);
}
}
@@ -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);
}
}
@@ -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];
@@ -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() {}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -102,8 +102,9 @@ public class LauncherRecentsView extends RecentsView<Launcher> 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<Launcher> 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<Launcher> 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);
}
}
}
@@ -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();
@@ -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<T extends BaseActivity> 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<T extends BaseActivity> 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<T extends BaseActivity> extends PagedView impl
return null;
}
@Override
public void onTaskIconChanged(String pkg, UserHandle user) {
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView tv = getTaskViewAt(i);
Task task = tv.getTask();
if (task != null && task.key != null && pkg.equals(task.key.getPackageName())
&& task.key.userId == user.getIdentifier()) {
task.icon = null;
if (tv.getIconView().getDrawable() != null) {
tv.onTaskListVisibilityChanged(true /* visible */);
}
}
}
}
/**
* Update the thumbnail of the task.
* @param refreshNow Refresh immediately if it's true.
@@ -468,6 +484,11 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
public void setOverviewStateEnabled(boolean enabled) {
mOverviewStateEnabled = enabled;
updateTaskStackListenerState();
if (!enabled) {
// Reset the running task when leaving overview since it can still have a reference to
// its thumbnail
mTmpRunningTask = null;
}
}
public void onDigitalWellbeingToastShown() {
@@ -859,8 +880,8 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
*/
public void onSwipeUpAnimationSuccess() {
if (getRunningTaskView() != null) {
float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlay != null
? mLiveTileOverlay.cancelIconAnimation()
float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlayAttached
? LiveTileOverlay.getInstance().cancelIconAnimation()
: 0f;
animateUpRunningTaskIconScale(startProgress);
}
@@ -1672,8 +1693,8 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
final int[] visibleTasks = getVisibleChildrenRange();
event.setFromIndex(taskViewCount - visibleTasks[1] - 1);
event.setToIndex(taskViewCount - visibleTasks[0] - 1);
event.setFromIndex(taskViewCount - visibleTasks[1]);
event.setToIndex(taskViewCount - visibleTasks[0]);
event.setItemCount(taskViewCount);
}
}
@@ -1707,13 +1728,13 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
mAppWindowAnimationHelper = appWindowAnimationHelper;
}
public void setLiveTileOverlay(LiveTileOverlay liveTileOverlay) {
mLiveTileOverlay = liveTileOverlay;
public void setLiveTileOverlayAttached(boolean liveTileOverlayAttached) {
mLiveTileOverlayAttached = liveTileOverlayAttached;
}
public void updateLiveTileIcon(Drawable icon) {
if (mLiveTileOverlay != null) {
mLiveTileOverlay.setIcon(icon);
if (mLiveTileOverlayAttached) {
LiveTileOverlay.getInstance().setIcon(icon);
}
}
@@ -1725,7 +1746,17 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
return;
}
mRecentsAnimationController.finish(toRecents, onFinishComplete);
mRecentsAnimationController.finish(toRecents, () -> {
if (onFinishComplete != null) {
onFinishComplete.run();
// After we finish the recents animation, the current task id should be correctly
// reset so that when the task is launched from Overview later, it goes through the
// flow of starting a new task instead of finishing recents animation to app. A
// typical example of this is (1) user swipes up from app to Overview (2) user
// taps on QSB (3) user goes back to Overview and launch the most recent task.
setCurrentTask(-1);
}
});
}
public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) {
@@ -1828,8 +1859,8 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
private void updateEnabledOverlays() {
int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1;
int taskCount = getTaskViewCount();
for (int i = 0; i < taskCount; i++) {
getTaskViewAt(i).setOverlayEnabled(i == overlayEnabledPage);
for (int i = mTaskViewStartIndex; i < mTaskViewStartIndex + taskCount; i++) {
getTaskViewAtByAbsoluteIndex(i).setOverlayEnabled(i == overlayEnabledPage);
}
}
@@ -1850,20 +1881,20 @@ public abstract class RecentsView<T extends BaseActivity> 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
@@ -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<TaskSystemShortcut> 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);
}
@@ -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<Boolean> 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<TaskSystemShortcut> 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<TaskSystemShortcut> 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;
}
}
+2 -2
View File
@@ -19,8 +19,6 @@
<!-- Activity which blocks home gesture -->
<string name="gesture_blocking_activity" translatable="false"></string>
<string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
<string name="stats_log_manager_class" translatable="false">com.android.quickstep.logging.StatsLogCompatManager</string>
<string name="test_information_handler_class" translatable="false">com.android.quickstep.QuickstepTestInformationHandler</string>
@@ -33,4 +31,6 @@
<!-- Assistant Gesture -->
<integer name="assistant_gesture_min_time_threshold">200</integer>
<integer name="assistant_gesture_corner_deg_threshold">20</integer>
<string name="wellbeing_provider_pkg" translatable="false"></string>
</resources>
@@ -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<SystemShortcut.Factory> getSupportedShortcuts() {
return Stream.concat(super.getSupportedShortcuts(),
Stream.of(WellbeingModel.SHORTCUT_FACTORY));
}
public ShelfPeekAnim getShelfPeekAnim() {
return mShelfPeekAnim;
}
}
@@ -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<Launcher> {
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<Launcher, Boolean> onInitListener) {
super(onInitListener, Launcher.ACTIVITY_TRACKER);
}
@@ -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<String, RemoteAction> mActionIdMap = new ArrayMap<>();
private final Map<String, String> 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);
}
@@ -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;
}
}
@@ -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);
}
@@ -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;
}
}
@@ -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);
}
}
}
@@ -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);
}
}
@@ -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<T extends BaseDraggingActivity> {
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<AnimatorPlaybackController> callback);
AnimationFactory prepareRecentsUI(boolean activityVisible, boolean animateActivity,
Consumer<AnimatorPlaybackController> callback);
ActivityInitListener createActivityInitListener(BiPredicate<T, Boolean> onInitListener);
ActivityInitListener createActivityInitListener(Predicate<Boolean> 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<T extends BaseDraggingActivity> {
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<T extends BaseDraggingActivity> {
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<T extends BaseDraggingActivity> {
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
@@ -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
@@ -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<String> 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 <T extends BaseDraggingActivity> BaseActivityInterface<T> 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);
}
}
@@ -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() { }
@@ -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<Runnable> mCallbacks = new SparseArray<>();
private final SparseArray<Consumer<Boolean>> mStateChangeHandlers = new SparseArray<>();
private final SparseArray<LinkedList<Runnable>> mCallbacks = new SparseArray<>();
private final SparseArray<ArrayList<Consumer<Boolean>>> 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<Runnable> 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<Consumer<Boolean>> listeners = mStateChangeListeners.valueAt(i);
for (Consumer<Boolean> 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<Runnable> 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<Boolean> handler) {
mStateChangeHandlers.put(stateMask, handler);
public void addChangeListener(int stateMask, Consumer<Boolean> listener) {
final ArrayList<Consumer<Boolean>> listeners;
if (mStateChangeListeners.indexOfKey(stateMask) >= 0) {
listeners = mStateChangeListeners.get(stateMask);
} else {
listeners = new ArrayList<>();
mStateChangeListeners.put(stateMask, listeners);
}
listeners.add(listener);
}
public int getState() {
@@ -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<BitmapInfo> mDefaultIcons = new SparseArray<>();
private final DrawableFactory mDrawableFactory;
private final boolean mDisableColorExtraction;
public NormalizedIconLoader(Context context, TaskKeyLruCache<Drawable> iconCache,
LruCache<ComponentName, ActivityInfo> 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);
}
}
@@ -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) {}
}
}
@@ -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<RecentsAnimationController> mOnFinishedListener;
private final boolean mShouldMinimizeSplitScreen;
private boolean mWindowThresholdCrossed = false;
private InputConsumerController mInputConsumerController;
private Supplier<InputConsumer> 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();
@@ -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<Runnable> 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);
}
@@ -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);
}
}
@@ -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<RecentsModel> INSTANCE =
new MainThreadInitializedObject<>(RecentsModel::new);
private final List<TaskThumbnailChangeListener> mThumbnailChangeListeners = new ArrayList<>();
private final List<TaskVisualsChangeListener> 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);
}
}
@@ -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
}
}
@@ -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<Drawable> mIconCache;
private final TaskKeyLruCache<String> mContentDescriptionCache;
private final LruCache<ComponentName, ActivityInfo> 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<TaskCacheEntry> mIconCache;
private final SparseArray<BitmapInfo> 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<ComponentName, ActivityInfo> 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 = "";
}
}
@@ -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<ThumbnailData> 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<ThumbnailData> {
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);
}
}
}
}
@@ -20,10 +20,10 @@ import android.util.Log;
import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CANCEL_TARGET;
import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
import static com.android.systemui.shared.system.LauncherEventUtil.DISMISS;
import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_QUICK_SCRUB_ONBOARDING_TIP;
import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_SWIPE_UP_ONBOARDING_TIP;
import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -31,6 +31,11 @@ public class ActivityInitListener<T extends BaseActivity> implements SchedulerCa
private final BiPredicate<T, Boolean> mOnInitListener;
private final ActivityTracker<T> 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<T, Boolean> onInitListener,
ActivityTracker<T> tracker) {
mOnInitListener = onInitListener;
@@ -42,6 +47,10 @@ public class ActivityInitListener<T extends BaseActivity> 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);
}
@@ -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);
}
@@ -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);
}
}
@@ -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 <V> The type of the value
*/
public class TaskKeyLruCache<V> {
private final MyLinkedHashMap<V> 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<TaskKey> 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<V> 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<V> entry = mMap.get(taskId);
if (entry != null) {
entry.mValue = data;
}
}
private static class Entry<V> {
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<V> extends LinkedHashMap<Integer, Entry<V>> {
private final int mMaxSize;
MyLinkedHashMap(int maxSize) {
super(0, 0.75f, true /* accessOrder */);
mMaxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Entry<Integer, TaskKeyLruCache.Entry<V>> eldest) {
return size() > mMaxSize;
}
}
}
@@ -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;
@@ -60,6 +60,7 @@ import com.android.launcher3.util.rule.FailureWatcher;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import com.android.quickstep.views.RecentsView;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
@@ -99,7 +100,7 @@ public class FallbackRecentsTest {
}
mOrderSensitiveRules = RuleChain.outerRule(new NavigationModeSwitchRule(mLauncher))
.around(new FailureWatcher(mDevice));
.around(new FailureWatcher(mDevice));
mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
getHomeIntentInPackage(context),
@@ -129,6 +130,7 @@ public class FallbackRecentsTest {
@NavigationModeSwitch
@Test
@Ignore // b/143488140
public void goToOverviewFromHome() {
mDevice.pressHome();
assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg(
@@ -139,6 +141,7 @@ public class FallbackRecentsTest {
@NavigationModeSwitch
@Test
@Ignore // b/143488140
public void goToOverviewFromApp() {
startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
@@ -162,7 +165,7 @@ public class FallbackRecentsTest {
}
result[0] = f.apply(activity);
return true;
}).get(), DEFAULT_UI_TIMEOUT);
}).get(), DEFAULT_UI_TIMEOUT, mLauncher);
return (T) result[0];
}
@@ -173,12 +176,13 @@ public class FallbackRecentsTest {
@NavigationModeSwitch
@Test
@Ignore // b/143488140
public void testOverview() {
startAppFastAndWaitForRecentTask(getAppPackageName());
startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
startTestActivity(2);
Wait.atMost("Expected three apps in the task list",
() -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT);
() -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
BaseOverview overview = mLauncher.getBackground().switchToOverview();
executeOnRecents(recents ->
@@ -237,7 +241,8 @@ public class FallbackRecentsTest {
private void startAppFastAndWaitForRecentTask(String packageName) {
startAppFast(packageName);
Wait.atMost("Expected app in task list",
() -> containsRecentTaskWithPackage(packageName), DEFAULT_ACTIVITY_TIMEOUT);
() -> containsRecentTaskWithPackage(packageName), DEFAULT_ACTIVITY_TIMEOUT,
mLauncher);
}
private boolean containsRecentTaskWithPackage(String packageName) {
@@ -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;
@@ -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;
@@ -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();
}
}

Some files were not shown because too many files have changed in this diff Show More