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:
+3
-3
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+6
-13
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
+404
@@ -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();
|
||||
}
|
||||
}
|
||||
+37
-2
@@ -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);
|
||||
|
||||
+1
-5
@@ -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;
|
||||
}
|
||||
|
||||
+38
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+34
-32
@@ -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;
|
||||
|
||||
+96
-73
@@ -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> {
|
||||
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
+15
-12
@@ -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
|
||||
|
||||
+3
-2
@@ -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 {
|
||||
|
||||
|
||||
+5
-5
@@ -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();
|
||||
|
||||
+32
-7
@@ -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();
|
||||
|
||||
+7
-3
@@ -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
|
||||
|
||||
+17
-13
@@ -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);
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+2
-2
@@ -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 {
|
||||
|
||||
+25
-9
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+74
-81
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
+99
-79
@@ -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);
|
||||
}
|
||||
}
|
||||
+206
-180
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+1
-7
@@ -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.
|
||||
|
||||
+157
-154
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
-5
@@ -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;
|
||||
|
||||
+13
-19
@@ -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);
|
||||
}
|
||||
|
||||
+33
-53
@@ -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;
|
||||
|
||||
+27
-52
@@ -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;
|
||||
}
|
||||
}
|
||||
+19
-18
@@ -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.
|
||||
|
||||
+6
-6
@@ -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 */);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+47
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
+8
-7
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-4
@@ -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() { }
|
||||
|
||||
|
||||
+64
-24
@@ -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
Reference in New Issue
Block a user