Merge remote-tracking branch 'aosp/android12L-release' into 12.1-dev
This commit is contained in:
@@ -22,21 +22,25 @@ import static com.android.launcher3.LauncherState.NORMAL;
|
||||
import static com.android.launcher3.LauncherState.NO_OFFSET;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
|
||||
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
|
||||
import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
|
||||
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.app.ActivityOptions;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.ServiceConnection;
|
||||
import android.graphics.Insets;
|
||||
import android.hardware.SensorManager;
|
||||
import android.hardware.devicestate.DeviceStateManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.IBinder;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
import android.window.SplashScreen;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -53,9 +57,10 @@ import com.android.launcher3.statehandlers.DepthController;
|
||||
import com.android.launcher3.statemanager.StateManager.StateHandler;
|
||||
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
|
||||
import com.android.launcher3.taskbar.TaskbarManager;
|
||||
import com.android.launcher3.taskbar.TaskbarStateHandler;
|
||||
import com.android.launcher3.uioverrides.RecentsViewStateController;
|
||||
import com.android.launcher3.util.ActivityOptionsWrapper;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.IntSet;
|
||||
import com.android.launcher3.util.ObjectWrapper;
|
||||
import com.android.launcher3.util.UiThreadHelper;
|
||||
import com.android.quickstep.OverviewCommandHelper;
|
||||
@@ -65,18 +70,24 @@ import com.android.quickstep.SysUINavigationMode.Mode;
|
||||
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.TaskUtils;
|
||||
import com.android.quickstep.TouchInteractionService;
|
||||
import com.android.quickstep.TouchInteractionService.TISBinder;
|
||||
import com.android.quickstep.util.LauncherUnfoldAnimationController;
|
||||
import com.android.quickstep.util.ProxyScreenStatusProvider;
|
||||
import com.android.quickstep.util.RemoteAnimationProvider;
|
||||
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
|
||||
import com.android.quickstep.util.SplitSelectStateController;
|
||||
import com.android.quickstep.util.TISBindHelper;
|
||||
import com.android.quickstep.views.OverviewActionsView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.SplitPlaceholderView;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.ActivityOptionsCompat;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
import com.android.systemui.unfold.UnfoldTransitionFactory;
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
|
||||
import com.android.systemui.unfold.config.UnfoldTransitionConfig;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -98,45 +109,61 @@ public abstract class BaseQuickstepLauncher extends Launcher
|
||||
|
||||
private OverviewActionsView mActionsView;
|
||||
|
||||
private TISBindHelper mTISBindHelper;
|
||||
private @Nullable TaskbarManager mTaskbarManager;
|
||||
private @Nullable OverviewCommandHelper mOverviewCommandHelper;
|
||||
private @Nullable LauncherTaskbarUIController mTaskbarUIController;
|
||||
private final ServiceConnection mTisBinderConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
||||
mTaskbarManager = ((TISBinder) iBinder).getTaskbarManager();
|
||||
mTaskbarManager.setLauncher(BaseQuickstepLauncher.this);
|
||||
|
||||
mOverviewCommandHelper = ((TISBinder) iBinder).getOverviewCommandHelper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) { }
|
||||
};
|
||||
private final TaskbarStateHandler mTaskbarStateHandler = new TaskbarStateHandler(this);
|
||||
|
||||
// Will be updated when dragging from taskbar.
|
||||
private @Nullable DragOptions mNextWorkspaceDragOptions = null;
|
||||
private SplitPlaceholderView mSplitPlaceholderView;
|
||||
|
||||
private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
|
||||
private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this);
|
||||
addMultiWindowModeChangedListener(mDepthController);
|
||||
initUnfoldTransitionProgressProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (mLauncherUnfoldAnimationController != null) {
|
||||
mLauncherUnfoldAnimationController.onResume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
if (mLauncherUnfoldAnimationController != null) {
|
||||
mLauncherUnfoldAnimationController.onPause();
|
||||
}
|
||||
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
mAppTransitionManager.onActivityDestroyed();
|
||||
if (mUnfoldTransitionProgressProvider != null) {
|
||||
mUnfoldTransitionProgressProvider.destroy();
|
||||
}
|
||||
|
||||
SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
|
||||
|
||||
|
||||
unbindService(mTisBinderConnection);
|
||||
mTISBindHelper.onDestroy();
|
||||
if (mTaskbarManager != null) {
|
||||
mTaskbarManager.setLauncher(null);
|
||||
mTaskbarManager.clearActivity(this);
|
||||
}
|
||||
|
||||
if (mLauncherUnfoldAnimationController != null) {
|
||||
mLauncherUnfoldAnimationController.onDestroy();
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@@ -200,6 +227,17 @@ public abstract class BaseQuickstepLauncher extends Launcher
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code LauncherOverlayCallbacks} scroll amount.
|
||||
* Indicates transition progress to -1 screen.
|
||||
* @param progress From 0 to 1.
|
||||
*/
|
||||
@Override
|
||||
public void onScrollChanged(float progress) {
|
||||
super.onScrollChanged(progress);
|
||||
mDepthController.onOverlayScrollChanged(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startIntentSenderForResult(IntentSender intent, int requestCode,
|
||||
Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
|
||||
@@ -261,32 +299,64 @@ public abstract class BaseQuickstepLauncher extends Launcher
|
||||
|
||||
SysUINavigationMode.INSTANCE.get(this).updateMode();
|
||||
mActionsView = findViewById(R.id.overview_actions_view);
|
||||
mSplitPlaceholderView = findViewById(R.id.split_placeholder);
|
||||
RecentsView overviewPanel = (RecentsView) getOverviewPanel();
|
||||
mSplitPlaceholderView.init(
|
||||
new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this))
|
||||
);
|
||||
overviewPanel.init(mActionsView, mSplitPlaceholderView);
|
||||
SplitSelectStateController controller =
|
||||
new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this),
|
||||
getStateManager(), getDepthController());
|
||||
overviewPanel.init(mActionsView, controller);
|
||||
mActionsView.setDp(getDeviceProfile());
|
||||
mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
|
||||
|
||||
mAppTransitionManager = new QuickstepTransitionManager(this);
|
||||
mAppTransitionManager.registerRemoteAnimations();
|
||||
mAppTransitionManager.registerRemoteTransitions();
|
||||
|
||||
bindService(new Intent(this, TouchInteractionService.class), mTisBinderConnection, 0);
|
||||
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
|
||||
}
|
||||
|
||||
private void onTISConnected(TISBinder binder) {
|
||||
mTaskbarManager = binder.getTaskbarManager();
|
||||
mTaskbarManager.setActivity(this);
|
||||
mOverviewCommandHelper = binder.getOverviewCommandHelper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runOnBindToTouchInteractionService(Runnable r) {
|
||||
mTISBindHelper.runOnBindToTouchInteractionService(r);
|
||||
}
|
||||
|
||||
private void initUnfoldTransitionProgressProvider() {
|
||||
final UnfoldTransitionConfig config = UnfoldTransitionFactory.createConfig(this);
|
||||
if (config.isEnabled()) {
|
||||
mUnfoldTransitionProgressProvider =
|
||||
UnfoldTransitionFactory.createUnfoldTransitionProgressProvider(
|
||||
this,
|
||||
config,
|
||||
ProxyScreenStatusProvider.INSTANCE,
|
||||
getSystemService(DeviceStateManager.class),
|
||||
getSystemService(SensorManager.class),
|
||||
getMainThreadHandler(),
|
||||
getMainExecutor()
|
||||
);
|
||||
|
||||
mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
|
||||
this,
|
||||
getWindowManager(),
|
||||
mUnfoldTransitionProgressProvider
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) {
|
||||
mTaskbarUIController = taskbarUIController;
|
||||
}
|
||||
|
||||
public <T extends OverviewActionsView> T getActionsView() {
|
||||
return (T) mActionsView;
|
||||
public @Nullable LauncherTaskbarUIController getTaskbarUIController() {
|
||||
return mTaskbarUIController;
|
||||
}
|
||||
|
||||
public SplitPlaceholderView getSplitPlaceholderView() {
|
||||
return mSplitPlaceholderView;
|
||||
public <T extends OverviewActionsView> T getActionsView() {
|
||||
return (T) mActionsView;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -301,19 +371,15 @@ public abstract class BaseQuickstepLauncher extends Launcher
|
||||
out.add(getDepthController());
|
||||
out.add(new RecentsViewStateController(this));
|
||||
out.add(new BackButtonAlphaHandler(this));
|
||||
out.add(getTaskbarStateHandler());
|
||||
}
|
||||
|
||||
public DepthController getDepthController() {
|
||||
return mDepthController;
|
||||
}
|
||||
|
||||
public @Nullable LauncherTaskbarUIController getTaskbarUIController() {
|
||||
return mTaskbarUIController;
|
||||
}
|
||||
|
||||
public TaskbarStateHandler getTaskbarStateHandler() {
|
||||
return mTaskbarStateHandler;
|
||||
@Nullable
|
||||
public UnfoldTransitionProgressProvider getUnfoldTransitionProgressProvider() {
|
||||
return mUnfoldTransitionProgressProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -363,14 +429,6 @@ public abstract class BaseQuickstepLauncher extends Launcher
|
||||
? new float[] {1, 1} : new float[] {1.1f, NO_OFFSET};
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getNormalTaskbarScale() {
|
||||
if (mTaskbarUIController != null) {
|
||||
return mTaskbarUIController.getTaskbarScaleOnHome();
|
||||
}
|
||||
return super.getNormalTaskbarScale();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragLayerHierarchyChanged() {
|
||||
onLauncherStateOrFocusChanged();
|
||||
@@ -425,8 +483,8 @@ public abstract class BaseQuickstepLauncher extends Launcher
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishBindingItems(int pageBoundFirst) {
|
||||
super.finishBindingItems(pageBoundFirst);
|
||||
public void finishBindingItems(IntSet pagesBoundFirst) {
|
||||
super.finishBindingItems(pagesBoundFirst);
|
||||
// Instantiate and initialize WellbeingModel now that its loading won't interfere with
|
||||
// populating workspace.
|
||||
// TODO: Find a better place for this
|
||||
@@ -496,4 +554,35 @@ public abstract class BaseQuickstepLauncher extends Launcher
|
||||
public void setHintUserWillBeActive() {
|
||||
addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
|
||||
super.onDisplayInfoChanged(context, info, flags);
|
||||
// When changing screens, force moving to rest state similar to StatefulActivity.onStop, as
|
||||
// StatefulActivity isn't called consistently.
|
||||
if ((flags & CHANGE_ACTIVE_SCREEN) != 0) {
|
||||
getStateManager().moveToRestState();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
|
||||
super.dump(prefix, fd, writer, args);
|
||||
if (mDepthController != null) {
|
||||
mDepthController.dump(prefix, writer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateWindowInsets(WindowInsets.Builder updatedInsetsBuilder,
|
||||
WindowInsets oldInsets) {
|
||||
// Override the tappable insets to be 0 on the bottom for gesture nav (otherwise taskbar
|
||||
// would count towards it). This is used for the bottom protection in All Apps for example.
|
||||
if (SysUINavigationMode.getMode(this) == NO_BUTTON) {
|
||||
Insets oldTappableInsets = oldInsets.getInsets(WindowInsets.Type.tappableElement());
|
||||
Insets newTappableInsets = Insets.of(oldTappableInsets.left, oldTappableInsets.top,
|
||||
oldTappableInsets.right, 0);
|
||||
updatedInsetsBuilder.setInsets(WindowInsets.Type.tappableElement(), newTappableInsets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,8 @@ package com.android.launcher3;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.Handler;
|
||||
|
||||
import com.android.quickstep.util.ActivityInitListener;
|
||||
import com.android.quickstep.util.RemoteAnimationProvider;
|
||||
@@ -78,11 +75,4 @@ public class LauncherInitListener extends ActivityInitListener<Launcher> {
|
||||
mRemoteAnimationProvider = null;
|
||||
super.unregister();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
|
||||
Context context, Handler handler, long duration) {
|
||||
mRemoteAnimationProvider = animProvider;
|
||||
super.registerAndStartActivity(intent, animProvider, context, handler, duration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,19 +28,24 @@ import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
|
||||
import static com.android.launcher3.LauncherState.ALL_APPS;
|
||||
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.launcher3.Utilities.mapBoundToRange;
|
||||
import static com.android.launcher3.Utilities.postAsyncCallback;
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
|
||||
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
|
||||
import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_BACK_SWIPE_HOME_ANIMATION;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_SCRIM_FOR_APP_LAUNCH;
|
||||
import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
|
||||
import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
|
||||
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
|
||||
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
|
||||
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
|
||||
import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
|
||||
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
|
||||
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
|
||||
import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
|
||||
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
|
||||
@@ -52,26 +57,31 @@ import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Pair;
|
||||
import android.util.Size;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.View;
|
||||
import android.view.ViewRootImpl;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.PathInterpolator;
|
||||
|
||||
@@ -86,8 +96,12 @@ import com.android.launcher3.dragndrop.DragLayer;
|
||||
import com.android.launcher3.icons.FastBitmapDrawable;
|
||||
import com.android.launcher3.shortcuts.DeepShortcutView;
|
||||
import com.android.launcher3.statehandlers.DepthController;
|
||||
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
|
||||
import com.android.launcher3.touch.PagedOrientationHandler;
|
||||
import com.android.launcher3.util.ActivityOptionsWrapper;
|
||||
import com.android.launcher3.util.DynamicResource;
|
||||
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
|
||||
import com.android.launcher3.util.ObjectWrapper;
|
||||
import com.android.launcher3.util.RunnableList;
|
||||
import com.android.launcher3.views.FloatingIconView;
|
||||
import com.android.launcher3.views.ScrimView;
|
||||
@@ -96,7 +110,9 @@ import com.android.quickstep.RemoteAnimationTargets;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.TaskViewUtils;
|
||||
import com.android.quickstep.util.MultiValueUpdateListener;
|
||||
import com.android.quickstep.util.RectFSpringAnim;
|
||||
import com.android.quickstep.util.RemoteAnimationProvider;
|
||||
import com.android.quickstep.util.StaggeredWorkspaceAnim;
|
||||
import com.android.quickstep.util.SurfaceTransactionApplier;
|
||||
import com.android.quickstep.util.WorkspaceRevealAnim;
|
||||
import com.android.quickstep.views.FloatingWidgetView;
|
||||
@@ -144,21 +160,11 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
|
||||
"android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
|
||||
|
||||
private static final long APP_LAUNCH_DURATION = 450;
|
||||
// Use a shorter duration for x or y translation to create a curve effect
|
||||
private static final long APP_LAUNCH_CURVED_DURATION = 250;
|
||||
private static final long APP_LAUNCH_DURATION = 500;
|
||||
|
||||
private static final long APP_LAUNCH_ALPHA_DURATION = 50;
|
||||
private static final long APP_LAUNCH_ALPHA_START_DELAY = 25;
|
||||
|
||||
// We scale the durations for the downward app launch animations (minus the scale animation).
|
||||
private static final float APP_LAUNCH_DOWN_DUR_SCALE_FACTOR = 0.8f;
|
||||
private static final long APP_LAUNCH_DOWN_DURATION =
|
||||
(long) (APP_LAUNCH_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
|
||||
private static final long APP_LAUNCH_DOWN_CURVED_DURATION =
|
||||
(long) (APP_LAUNCH_CURVED_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
|
||||
private static final long APP_LAUNCH_ALPHA_DOWN_DURATION =
|
||||
(long) (APP_LAUNCH_ALPHA_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR);
|
||||
|
||||
public static final int ANIMATION_NAV_FADE_IN_DURATION = 266;
|
||||
public static final int ANIMATION_NAV_FADE_OUT_DURATION = 133;
|
||||
public static final long ANIMATION_DELAY_NAV_FADE_IN =
|
||||
@@ -168,12 +174,11 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
public static final Interpolator NAV_FADE_OUT_INTERPOLATOR =
|
||||
new PathInterpolator(0.2f, 0f, 1f, 1f);
|
||||
|
||||
private static final long CROP_DURATION = 375;
|
||||
private static final long RADIUS_DURATION = 375;
|
||||
|
||||
public static final int RECENTS_LAUNCH_DURATION = 336;
|
||||
private static final int LAUNCHER_RESUME_START_DELAY = 100;
|
||||
private static final int CLOSING_TRANSITION_DURATION_MS = 250;
|
||||
public static final int SPLIT_LAUNCH_DURATION = 370;
|
||||
public static final int SPLIT_DIVIDER_ANIM_DURATION = 100;
|
||||
|
||||
public static final int CONTENT_ALPHA_DURATION = 217;
|
||||
protected static final int CONTENT_SCALE_DURATION = 350;
|
||||
@@ -224,6 +229,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
// Will never be larger than MAX_NUM_TASKS
|
||||
private LinkedHashMap<Integer, Pair<Integer, Integer>> mTaskStartParams;
|
||||
|
||||
private final Interpolator mOpeningXInterpolator;
|
||||
private final Interpolator mOpeningInterpolator;
|
||||
|
||||
public QuickstepTransitionManager(Context context) {
|
||||
mLauncher = Launcher.cast(Launcher.getLauncher(context));
|
||||
mDragLayer = mLauncher.getDragLayer();
|
||||
@@ -250,6 +258,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(
|
||||
mStartingWindowListener);
|
||||
}
|
||||
|
||||
mOpeningXInterpolator = AnimationUtils.loadInterpolator(context, R.interpolator.app_open_x);
|
||||
mOpeningInterpolator = AnimationUtils.loadInterpolator(context,
|
||||
R.interpolator.three_point_fast_out_extra_slow_in);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -277,7 +289,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION
|
||||
- STATUS_BAR_TRANSITION_PRE_DELAY;
|
||||
RemoteAnimationAdapterCompat adapterCompat =
|
||||
new RemoteAnimationAdapterCompat(runner, duration, statusBarTransitionDelay);
|
||||
new RemoteAnimationAdapterCompat(runner, duration, statusBarTransitionDelay,
|
||||
mLauncher.getIApplicationThread());
|
||||
return new ActivityOptionsWrapper(
|
||||
ActivityOptionsCompat.makeRemoteAnimation(adapterCompat), onEndCallback);
|
||||
}
|
||||
@@ -356,7 +369,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
if (launcherClosing) {
|
||||
// Delay animation by a frame to avoid jank.
|
||||
Pair<AnimatorSet, Runnable> launcherContentAnimator =
|
||||
getLauncherContentAnimator(true /* isAppOpening */, startDelay);
|
||||
getLauncherContentAnimator(true /* isAppOpening */, startDelay, false);
|
||||
anim.play(launcherContentAnimator.first);
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
@@ -430,6 +443,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
4 - rotationChange);
|
||||
}
|
||||
}
|
||||
if (mDeviceProfile.isTaskbarPresentInApps) {
|
||||
// Animate to above the taskbar.
|
||||
bounds.bottom -= target.contentInsets.bottom;
|
||||
}
|
||||
return bounds;
|
||||
}
|
||||
|
||||
@@ -449,9 +466,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
* @param isAppOpening True when this is called when an app is opening.
|
||||
* False when this is called when an app is closing.
|
||||
* @param startDelay Start delay duration.
|
||||
* @param skipAllAppsScale True if we want to avoid scaling All Apps
|
||||
*/
|
||||
private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening,
|
||||
int startDelay) {
|
||||
int startDelay, boolean skipAllAppsScale) {
|
||||
AnimatorSet launcherAnimator = new AnimatorSet();
|
||||
Runnable endListener;
|
||||
|
||||
@@ -469,7 +487,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
final float startAlpha = appsView.getAlpha();
|
||||
final float startScale = SCALE_PROPERTY.get(appsView);
|
||||
appsView.setAlpha(alphas[0]);
|
||||
SCALE_PROPERTY.set(appsView, scales[0]);
|
||||
|
||||
ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas);
|
||||
alpha.setDuration(CONTENT_ALPHA_DURATION);
|
||||
@@ -481,12 +498,16 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
appsView.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
}
|
||||
});
|
||||
ObjectAnimator scale = ObjectAnimator.ofFloat(appsView, SCALE_PROPERTY, scales);
|
||||
scale.setInterpolator(AGGRESSIVE_EASE);
|
||||
scale.setDuration(CONTENT_SCALE_DURATION);
|
||||
|
||||
if (!skipAllAppsScale) {
|
||||
SCALE_PROPERTY.set(appsView, scales[0]);
|
||||
ObjectAnimator scale = ObjectAnimator.ofFloat(appsView, SCALE_PROPERTY, scales);
|
||||
scale.setInterpolator(AGGRESSIVE_EASE);
|
||||
scale.setDuration(CONTENT_SCALE_DURATION);
|
||||
launcherAnimator.play(scale);
|
||||
}
|
||||
|
||||
launcherAnimator.play(alpha);
|
||||
launcherAnimator.play(scale);
|
||||
|
||||
endListener = () -> {
|
||||
appsView.setAlpha(startAlpha);
|
||||
@@ -514,7 +535,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
|
||||
final boolean scrimEnabled = ENABLE_SCRIM_FOR_APP_LAUNCH.get();
|
||||
if (scrimEnabled) {
|
||||
int scrimColor = ColorTokens.OverviewScrim.resolveColor(mLauncher);
|
||||
boolean useTaskbarColor = mDeviceProfile.isTaskbarPresentInApps;
|
||||
int scrimColor = useTaskbarColor
|
||||
? mLauncher.getResources().getColor(R.color.taskbar_background)
|
||||
: ColorTokens.OverviewScrim.resolveColor(mLauncher);
|
||||
int scrimColorTrans = ColorUtils.setAlphaComponent(scrimColor, 0);
|
||||
int[] colors = isAppOpening
|
||||
? new int[]{scrimColorTrans, scrimColor}
|
||||
@@ -527,6 +551,30 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
colors);
|
||||
scrim.setDuration(CONTENT_SCRIM_DURATION);
|
||||
scrim.setInterpolator(DEACCEL_1_5);
|
||||
|
||||
if (useTaskbarColor) {
|
||||
// Hide the taskbar background color since it would duplicate the scrim.
|
||||
scrim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
LauncherTaskbarUIController taskbarUIController =
|
||||
mLauncher.getTaskbarUIController();
|
||||
if (taskbarUIController != null) {
|
||||
taskbarUIController.forceHideBackground(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
LauncherTaskbarUIController taskbarUIController =
|
||||
mLauncher.getTaskbarUIController();
|
||||
if (taskbarUIController != null) {
|
||||
taskbarUIController.forceHideBackground(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
launcherAnimator.play(scrim);
|
||||
}
|
||||
}
|
||||
@@ -619,10 +667,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
AnimOpenProperties prop = new AnimOpenProperties(mLauncher.getResources(), mDeviceProfile,
|
||||
windowTargetBounds, launcherIconBounds, v, dragLayerBounds[0], dragLayerBounds[1],
|
||||
hasSplashScreen, floatingView.isDifferentFromAppIcon());
|
||||
int left = (int) (prop.cropCenterXStart - prop.cropWidthStart / 2);
|
||||
int top = (int) (prop.cropCenterYStart - prop.cropHeightStart / 2);
|
||||
int right = (int) (left + prop.cropWidthStart);
|
||||
int bottom = (int) (top + prop.cropHeightStart);
|
||||
int left = prop.cropCenterXStart - prop.cropWidthStart / 2;
|
||||
int top = prop.cropCenterYStart - prop.cropHeightStart / 2;
|
||||
int right = left + prop.cropWidthStart;
|
||||
int bottom = top + prop.cropHeightStart;
|
||||
// Set the crop here so we can calculate the corner radius below.
|
||||
crop.set(left, top, right, bottom);
|
||||
|
||||
@@ -641,6 +689,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
if (v instanceof BubbleTextView) {
|
||||
((BubbleTextView) v).setStayPressed(false);
|
||||
}
|
||||
LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController();
|
||||
if (taskbarController != null) {
|
||||
taskbarController.showEdu();
|
||||
}
|
||||
openingTargets.release();
|
||||
}
|
||||
});
|
||||
@@ -649,31 +701,33 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
? Math.max(crop.width(), crop.height()) / 2f * IconShapeManager.getWindowTransitionRadius(mLauncher)
|
||||
: 0f;
|
||||
final float finalWindowRadius = mDeviceProfile.isMultiWindowMode
|
||||
? 0 : getWindowCornerRadius(mLauncher.getResources());
|
||||
? 0 : getWindowCornerRadius(mLauncher);
|
||||
final float finalShadowRadius = appTargetsAreTranslucent ? 0 : mMaxShadowRadius;
|
||||
|
||||
MultiValueUpdateListener listener = new MultiValueUpdateListener() {
|
||||
FloatProp mDx = new FloatProp(0, prop.dX, 0, prop.xDuration, AGGRESSIVE_EASE);
|
||||
FloatProp mDy = new FloatProp(0, prop.dY, 0, prop.yDuration, AGGRESSIVE_EASE);
|
||||
FloatProp mDx = new FloatProp(0, prop.dX, 0, APP_LAUNCH_DURATION,
|
||||
mOpeningXInterpolator);
|
||||
FloatProp mDy = new FloatProp(0, prop.dY, 0, APP_LAUNCH_DURATION,
|
||||
mOpeningInterpolator);
|
||||
|
||||
FloatProp mIconScaleToFitScreen = new FloatProp(prop.initialAppIconScale,
|
||||
prop.finalAppIconScale, 0, APP_LAUNCH_DURATION, EXAGGERATED_EASE);
|
||||
prop.finalAppIconScale, 0, APP_LAUNCH_DURATION, mOpeningInterpolator);
|
||||
FloatProp mIconAlpha = new FloatProp(prop.iconAlphaStart, 0f,
|
||||
APP_LAUNCH_ALPHA_START_DELAY, prop.alphaDuration, LINEAR);
|
||||
APP_LAUNCH_ALPHA_START_DELAY, APP_LAUNCH_ALPHA_DURATION, LINEAR);
|
||||
|
||||
FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, 0,
|
||||
RADIUS_DURATION, EXAGGERATED_EASE);
|
||||
APP_LAUNCH_DURATION, mOpeningInterpolator);
|
||||
FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius, 0,
|
||||
APP_LAUNCH_DURATION, EXAGGERATED_EASE);
|
||||
APP_LAUNCH_DURATION, mOpeningInterpolator);
|
||||
|
||||
FloatProp mCropRectCenterX = new FloatProp(prop.cropCenterXStart, prop.cropCenterXEnd,
|
||||
0, CROP_DURATION, EXAGGERATED_EASE);
|
||||
0, APP_LAUNCH_DURATION, mOpeningInterpolator);
|
||||
FloatProp mCropRectCenterY = new FloatProp(prop.cropCenterYStart, prop.cropCenterYEnd,
|
||||
0, CROP_DURATION, EXAGGERATED_EASE);
|
||||
0, APP_LAUNCH_DURATION, mOpeningInterpolator);
|
||||
FloatProp mCropRectWidth = new FloatProp(prop.cropWidthStart, prop.cropWidthEnd, 0,
|
||||
CROP_DURATION, EXAGGERATED_EASE);
|
||||
APP_LAUNCH_DURATION, mOpeningInterpolator);
|
||||
FloatProp mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd, 0,
|
||||
CROP_DURATION, EXAGGERATED_EASE);
|
||||
APP_LAUNCH_DURATION, mOpeningInterpolator);
|
||||
|
||||
FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION,
|
||||
NAV_FADE_OUT_INTERPOLATOR);
|
||||
@@ -810,7 +864,13 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
// Since we added a start delay, call update here to init the FloatingIconView properly.
|
||||
listener.onUpdate(0, true /* initOnly */);
|
||||
|
||||
animatorSet.playTogether(appAnimator, getBackgroundAnimator(appTargets));
|
||||
// If app targets are translucent, do not animate the background as it causes a visible
|
||||
// flicker when it resets itself at the end of its animation.
|
||||
if (appTargetsAreTranslucent) {
|
||||
animatorSet.play(appAnimator);
|
||||
} else {
|
||||
animatorSet.playTogether(appAnimator, getBackgroundAnimator());
|
||||
}
|
||||
return animatorSet;
|
||||
}
|
||||
|
||||
@@ -838,7 +898,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
}
|
||||
|
||||
final float finalWindowRadius = mDeviceProfile.isMultiWindowMode
|
||||
? 0 : getWindowCornerRadius(mLauncher.getResources());
|
||||
? 0 : getWindowCornerRadius(mLauncher);
|
||||
final FloatingWidgetView floatingView = FloatingWidgetView.getFloatingWidgetView(mLauncher,
|
||||
v, widgetBackgroundBounds,
|
||||
new Size(windowTargetBounds.width(), windowTargetBounds.height()),
|
||||
@@ -875,22 +935,23 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* delay */,
|
||||
WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR);
|
||||
final FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius,
|
||||
0 /* start */, RADIUS_DURATION, LINEAR);
|
||||
final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, 0, RADIUS_DURATION, LINEAR);
|
||||
0 /* start */, APP_LAUNCH_DURATION, mOpeningInterpolator);
|
||||
final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, 0, APP_LAUNCH_DURATION,
|
||||
mOpeningInterpolator);
|
||||
|
||||
// Window & widget background positioning bounds
|
||||
final FloatProp mDx = new FloatProp(widgetBackgroundBounds.centerX(),
|
||||
windowTargetBounds.centerX(), 0 /* delay */, APP_LAUNCH_CURVED_DURATION,
|
||||
EXAGGERATED_EASE);
|
||||
windowTargetBounds.centerX(), 0 /* delay */, APP_LAUNCH_DURATION,
|
||||
mOpeningXInterpolator);
|
||||
final FloatProp mDy = new FloatProp(widgetBackgroundBounds.centerY(),
|
||||
windowTargetBounds.centerY(), 0 /* delay */, APP_LAUNCH_DURATION,
|
||||
EXAGGERATED_EASE);
|
||||
mOpeningInterpolator);
|
||||
final FloatProp mWidth = new FloatProp(widgetBackgroundBounds.width(),
|
||||
windowTargetBounds.width(), 0 /* delay */, APP_LAUNCH_DURATION,
|
||||
EXAGGERATED_EASE);
|
||||
mOpeningInterpolator);
|
||||
final FloatProp mHeight = new FloatProp(widgetBackgroundBounds.height(),
|
||||
windowTargetBounds.height(), 0 /* delay */, APP_LAUNCH_DURATION,
|
||||
EXAGGERATED_EASE);
|
||||
mOpeningInterpolator);
|
||||
|
||||
final FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION,
|
||||
NAV_FADE_OUT_INTERPOLATOR);
|
||||
@@ -946,11 +1007,20 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
}
|
||||
});
|
||||
|
||||
animatorSet.playTogether(appAnimator, getBackgroundAnimator(appTargets));
|
||||
// If app targets are translucent, do not animate the background as it causes a visible
|
||||
// flicker when it resets itself at the end of its animation.
|
||||
if (appTargetsAreTranslucent) {
|
||||
animatorSet.play(appAnimator);
|
||||
} else {
|
||||
animatorSet.playTogether(appAnimator, getBackgroundAnimator());
|
||||
}
|
||||
return animatorSet;
|
||||
}
|
||||
|
||||
private ObjectAnimator getBackgroundAnimator(RemoteAnimationTargetCompat[] appTargets) {
|
||||
/**
|
||||
* Returns animator that controls depth/blur of the background.
|
||||
*/
|
||||
private ObjectAnimator getBackgroundAnimator() {
|
||||
// When launching an app from overview that doesn't map to a task, we still want to just
|
||||
// blur the wallpaper instead of the launcher surface as well
|
||||
boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW;
|
||||
@@ -1020,7 +1090,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
new RemoteAnimationAdapterCompat(
|
||||
new LauncherAnimationRunner(mHandler, mWallpaperOpenRunner,
|
||||
false /* startAtFrontOfQueue */),
|
||||
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
|
||||
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */,
|
||||
mLauncher.getIApplicationThread()));
|
||||
|
||||
if (KEYGUARD_ANIMATION.get()) {
|
||||
mKeyguardGoingAwayRunner = createWallpaperOpenRunner(true /* fromUnlock */);
|
||||
@@ -1030,7 +1101,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
new LauncherAnimationRunner(
|
||||
mHandler, mKeyguardGoingAwayRunner,
|
||||
true /* startAtFrontOfQueue */),
|
||||
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
|
||||
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */,
|
||||
mLauncher.getIApplicationThread()));
|
||||
}
|
||||
|
||||
new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
|
||||
@@ -1049,8 +1121,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
mWallpaperOpenTransitionRunner = createWallpaperOpenRunner(false /* fromUnlock */);
|
||||
mLauncherOpenTransition = RemoteAnimationAdapterCompat.buildRemoteTransition(
|
||||
new LauncherAnimationRunner(mHandler, mWallpaperOpenTransitionRunner,
|
||||
false /* startAtFrontOfQueue */));
|
||||
mLauncherOpenTransition.addHomeOpenCheck();
|
||||
false /* startAtFrontOfQueue */), mLauncher.getIApplicationThread());
|
||||
mLauncherOpenTransition.addHomeOpenCheck(mLauncher.getComponentName());
|
||||
SystemUiProxy.INSTANCE.getNoCreate().registerRemoteTransition(mLauncherOpenTransition);
|
||||
}
|
||||
}
|
||||
@@ -1091,7 +1163,16 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
}
|
||||
|
||||
private boolean launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode) {
|
||||
return taskIsATargetWithMode(targets, mLauncher.getTaskId(), mode);
|
||||
for (RemoteAnimationTargetCompat target : targets) {
|
||||
if (target.mode == mode && target.taskInfo != null
|
||||
// Compare component name instead of task-id because transitions will promote
|
||||
// the target up to the root task while getTaskId returns the leaf.
|
||||
&& target.taskInfo.topActivity != null
|
||||
&& target.taskInfo.topActivity.equals(mLauncher.getComponentName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1111,7 +1192,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1);
|
||||
unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
|
||||
float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 :
|
||||
QuickStepContract.getWindowCornerRadius(mLauncher.getResources());
|
||||
QuickStepContract.getWindowCornerRadius(mLauncher);
|
||||
unlockAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
@@ -1141,10 +1222,181 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
}
|
||||
|
||||
/**
|
||||
* Animator that controls the transformations of the windows the targets that are closing.
|
||||
* Returns view on launcher that corresponds to the closing app in the list of app targets
|
||||
*/
|
||||
private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] appTargets,
|
||||
RemoteAnimationTargetCompat[] wallpaperTargets) {
|
||||
private @Nullable View findLauncherView(RemoteAnimationTargetCompat[] appTargets) {
|
||||
for (RemoteAnimationTargetCompat appTarget : appTargets) {
|
||||
if (appTarget.mode == MODE_CLOSING) {
|
||||
View launcherView = findLauncherView(appTarget);
|
||||
if (launcherView != null) {
|
||||
return launcherView;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns view on launcher that corresponds to the {@param runningTaskTarget}.
|
||||
*/
|
||||
private @Nullable View findLauncherView(RemoteAnimationTargetCompat runningTaskTarget) {
|
||||
if (runningTaskTarget == null || runningTaskTarget.taskInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ComponentName[] taskInfoActivities = new ComponentName[] {
|
||||
runningTaskTarget.taskInfo.baseActivity,
|
||||
runningTaskTarget.taskInfo.origActivity,
|
||||
runningTaskTarget.taskInfo.realActivity,
|
||||
runningTaskTarget.taskInfo.topActivity};
|
||||
|
||||
String packageName = null;
|
||||
for (ComponentName component : taskInfoActivities) {
|
||||
if (component != null && component.getPackageName() != null) {
|
||||
packageName = component.getPackageName();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (packageName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find the associated item info for the launch cookie (if available), note that predicted
|
||||
// apps actually have an id of -1, so use another default id here
|
||||
final ArrayList<IBinder> launchCookies = runningTaskTarget.taskInfo.launchCookies == null
|
||||
? new ArrayList<>()
|
||||
: runningTaskTarget.taskInfo.launchCookies;
|
||||
|
||||
int launchCookieItemId = NO_MATCHING_ID;
|
||||
for (IBinder cookie : launchCookies) {
|
||||
Integer itemId = ObjectWrapper.unwrap(cookie);
|
||||
if (itemId != null) {
|
||||
launchCookieItemId = itemId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return mLauncher.getFirstMatchForAppClose(launchCookieItemId,
|
||||
packageName, UserHandle.of(runningTaskTarget.taskInfo.userId));
|
||||
}
|
||||
|
||||
private @NonNull RectF getDefaultWindowTargetRect() {
|
||||
RecentsView recentsView = mLauncher.getOverviewPanel();
|
||||
PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
|
||||
DeviceProfile dp = mLauncher.getDeviceProfile();
|
||||
final int halfIconSize = dp.iconSizePx / 2;
|
||||
float primaryDimension = orientationHandler
|
||||
.getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx);
|
||||
float secondaryDimension = orientationHandler
|
||||
.getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx);
|
||||
final float targetX = primaryDimension / 2f;
|
||||
final float targetY = secondaryDimension - dp.hotseatBarSizePx;
|
||||
return new RectF(targetX - halfIconSize, targetY - halfIconSize,
|
||||
targetX + halfIconSize, targetY + halfIconSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closing animator that animates the window into its final location on the workspace.
|
||||
*/
|
||||
private void getClosingWindowAnimators(AnimatorSet animation,
|
||||
RemoteAnimationTargetCompat[] targets, View launcherView, PointF velocityPxPerS) {
|
||||
FloatingIconView floatingIconView = null;
|
||||
FloatingWidgetView floatingWidget = null;
|
||||
RectF targetRect = new RectF();
|
||||
|
||||
RemoteAnimationTargetCompat runningTaskTarget = null;
|
||||
boolean isTransluscent = false;
|
||||
for (RemoteAnimationTargetCompat target : targets) {
|
||||
if (target.mode == MODE_CLOSING) {
|
||||
runningTaskTarget = target;
|
||||
isTransluscent = runningTaskTarget.isTranslucent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get floating view and target rect.
|
||||
if (launcherView instanceof LauncherAppWidgetHostView) {
|
||||
Size windowSize = new Size(mDeviceProfile.availableWidthPx,
|
||||
mDeviceProfile.availableHeightPx);
|
||||
int fallbackBackgroundColor =
|
||||
FloatingWidgetView.getDefaultBackgroundColor(mLauncher, runningTaskTarget);
|
||||
floatingWidget = FloatingWidgetView.getFloatingWidgetView(mLauncher,
|
||||
(LauncherAppWidgetHostView) launcherView, targetRect, windowSize,
|
||||
mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher),
|
||||
isTransluscent, fallbackBackgroundColor);
|
||||
} else if (launcherView != null) {
|
||||
floatingIconView = getFloatingIconView(mLauncher, launcherView,
|
||||
true /* hideOriginal */, targetRect, false /* isOpening */);
|
||||
} else {
|
||||
targetRect.set(getDefaultWindowTargetRect());
|
||||
}
|
||||
|
||||
final RectF startRect = new RectF(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);
|
||||
RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mLauncher,
|
||||
mDeviceProfile);
|
||||
|
||||
// Hook up floating views to the closing window animators.
|
||||
final int rotationChange = getRotationChange(targets);
|
||||
Rect windowTargetBounds = getWindowTargetBounds(targets, rotationChange);
|
||||
if (floatingIconView != null) {
|
||||
anim.addAnimatorListener(floatingIconView);
|
||||
floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged);
|
||||
floatingIconView.setFastFinishRunnable(anim::end);
|
||||
FloatingIconView finalFloatingIconView = floatingIconView;
|
||||
|
||||
// We want the window alpha to be 0 once this threshold is met, so that the
|
||||
// FolderIconView can be seen morphing into the icon shape.
|
||||
final float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION;
|
||||
|
||||
RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
|
||||
windowTargetBounds) {
|
||||
@Override
|
||||
public void onUpdate(RectF currentRectF, float progress) {
|
||||
finalFloatingIconView.update(1f, 255 /* fgAlpha */, currentRectF, progress,
|
||||
windowAlphaThreshold, getCornerRadius(progress), false);
|
||||
|
||||
super.onUpdate(currentRectF, progress);
|
||||
}
|
||||
};
|
||||
anim.addOnUpdateListener(runner);
|
||||
} else if (floatingWidget != null) {
|
||||
anim.addAnimatorListener(floatingWidget);
|
||||
floatingWidget.setOnTargetChangeListener(anim::onTargetPositionChanged);
|
||||
floatingWidget.setFastFinishRunnable(anim::end);
|
||||
|
||||
final float floatingWidgetAlpha = isTransluscent ? 0 : 1;
|
||||
FloatingWidgetView finalFloatingWidget = floatingWidget;
|
||||
RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
|
||||
windowTargetBounds) {
|
||||
@Override
|
||||
public void onUpdate(RectF currentRectF, float progress) {
|
||||
final float fallbackBackgroundAlpha =
|
||||
1 - mapBoundToRange(progress, 0.8f, 1, 0, 1, EXAGGERATED_EASE);
|
||||
final float foregroundAlpha =
|
||||
mapBoundToRange(progress, 0.5f, 1, 0, 1, EXAGGERATED_EASE);
|
||||
finalFloatingWidget.update(currentRectF, floatingWidgetAlpha, foregroundAlpha,
|
||||
fallbackBackgroundAlpha, 1 - progress);
|
||||
|
||||
super.onUpdate(currentRectF, progress);
|
||||
}
|
||||
};
|
||||
anim.addOnUpdateListener(runner);
|
||||
}
|
||||
|
||||
// Use a fixed velocity to start the animation.
|
||||
animation.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
anim.start(mLauncher, velocityPxPerS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Closing window animator that moves the window down and offscreen.
|
||||
*/
|
||||
private Animator getFallbackClosingWindowAnimators(RemoteAnimationTargetCompat[] appTargets) {
|
||||
final int rotationChange = getRotationChange(appTargets);
|
||||
SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer);
|
||||
Matrix matrix = new Matrix();
|
||||
@@ -1153,7 +1405,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
|
||||
int duration = CLOSING_TRANSITION_DURATION_MS;
|
||||
float windowCornerRadius = mDeviceProfile.isMultiWindowMode
|
||||
? 0 : getWindowCornerRadius(mLauncher.getResources());
|
||||
? 0 : getWindowCornerRadius(mLauncher);
|
||||
float startShadowRadius = areAllTargetsTranslucent(appTargets) ? 0 : mMaxShadowRadius;
|
||||
closingAnimator.setDuration(duration);
|
||||
closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
|
||||
@@ -1283,7 +1535,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
LauncherAnimationRunner.AnimationResult result) {
|
||||
if (mLauncher.isDestroyed()) {
|
||||
AnimatorSet anim = new AnimatorSet();
|
||||
anim.play(getClosingWindowAnimators(appTargets, wallpaperTargets));
|
||||
anim.play(getFallbackClosingWindowAnimators(appTargets));
|
||||
result.setAnimation(anim, mLauncher.getApplicationContext());
|
||||
return;
|
||||
}
|
||||
@@ -1310,9 +1562,39 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
|
||||
if (anim == null) {
|
||||
anim = new AnimatorSet();
|
||||
anim.play(mFromUnlock
|
||||
? getUnlockWindowAnimator(appTargets, wallpaperTargets)
|
||||
: getClosingWindowAnimators(appTargets, wallpaperTargets));
|
||||
|
||||
final boolean launcherIsForceInvisibleOrOpening = mLauncher.isForceInvisible()
|
||||
|| launcherIsATargetWithMode(appTargets, MODE_OPENING);
|
||||
|
||||
View launcherView = findLauncherView(appTargets);
|
||||
boolean playFallBackAnimation = (launcherView == null
|
||||
&& launcherIsForceInvisibleOrOpening)
|
||||
|| mLauncher.getWorkspace().isOverlayShown();
|
||||
|
||||
boolean playWorkspaceReveal = true;
|
||||
boolean skipAllAppsScale = false;
|
||||
if (mFromUnlock) {
|
||||
anim.play(getUnlockWindowAnimator(appTargets, wallpaperTargets));
|
||||
} else if (ENABLE_BACK_SWIPE_HOME_ANIMATION.get()
|
||||
&& !playFallBackAnimation) {
|
||||
// Use a fixed velocity to start the animation.
|
||||
float velocityPxPerS = DynamicResource.provider(mLauncher)
|
||||
.getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
|
||||
PointF velocity = new PointF(0, -velocityPxPerS);
|
||||
getClosingWindowAnimators(anim, appTargets, launcherView, velocity);
|
||||
if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
|
||||
anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
|
||||
true /* animateOverviewScrim */, launcherView).getAnimators());
|
||||
// We play StaggeredWorkspaceAnim as a part of the closing window animation.
|
||||
playWorkspaceReveal = false;
|
||||
} else {
|
||||
// Skip scaling all apps, otherwise FloatingIconView will get wrong
|
||||
// layout bounds.
|
||||
skipAllAppsScale = true;
|
||||
}
|
||||
} else {
|
||||
anim.play(getFallbackClosingWindowAnimators(appTargets));
|
||||
}
|
||||
|
||||
// Normally, we run the launcher content animation when we are transitioning
|
||||
// home, but if home is already visible, then we don't want to animate the
|
||||
@@ -1322,8 +1604,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
// targets list because it is already visible). In that case, we force
|
||||
// invisibility on touch down, and only reset it after the animation to home
|
||||
// is initialized.
|
||||
if (launcherIsATargetWithMode(appTargets, MODE_OPENING)
|
||||
|| mLauncher.isForceInvisible()) {
|
||||
if (launcherIsForceInvisibleOrOpening) {
|
||||
addCujInstrumentation(
|
||||
anim, InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
|
||||
// Only register the content animation for cancellation when state changes
|
||||
@@ -1331,7 +1612,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
|
||||
if (mLauncher.isInState(LauncherState.ALL_APPS)) {
|
||||
Pair<AnimatorSet, Runnable> contentAnimator =
|
||||
getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY);
|
||||
getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY,
|
||||
skipAllAppsScale);
|
||||
anim.play(contentAnimator.first);
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
@@ -1340,7 +1622,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
}
|
||||
});
|
||||
} else {
|
||||
anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators());
|
||||
if (playWorkspaceReveal) {
|
||||
anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1427,10 +1711,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
public final float dX;
|
||||
public final float dY;
|
||||
|
||||
public final long xDuration;
|
||||
public final long yDuration;
|
||||
public final long alphaDuration;
|
||||
|
||||
public final float initialAppIconScale;
|
||||
public final float finalAppIconScale;
|
||||
|
||||
@@ -1462,14 +1742,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
dX = centerX - launcherIconBounds.centerX();
|
||||
dY = centerY - launcherIconBounds.centerY();
|
||||
|
||||
boolean useUpwardAnimation = launcherIconBounds.top > centerY
|
||||
|| Math.abs(dY) < dp.cellHeightPx;
|
||||
xDuration = useUpwardAnimation ? APP_LAUNCH_CURVED_DURATION
|
||||
: APP_LAUNCH_DOWN_DURATION;
|
||||
yDuration = useUpwardAnimation ? APP_LAUNCH_DURATION
|
||||
: APP_LAUNCH_DOWN_CURVED_DURATION;
|
||||
alphaDuration = useUpwardAnimation ? APP_LAUNCH_ALPHA_DURATION
|
||||
: APP_LAUNCH_ALPHA_DOWN_DURATION;
|
||||
iconAlphaStart = hasSplashScreen && !hasDifferentAppIcon ? 0 : 1f;
|
||||
|
||||
final int windowIconSize = ResourceUtils.getDimenByName("starting_surface_icon_size",
|
||||
@@ -1501,4 +1773,101 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
mTransitionManager.mTaskStartParams.put(taskId, Pair.create(supportedType, color));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RectFSpringAnim update listener to be used for app to home animation.
|
||||
*/
|
||||
private class SpringAnimRunner implements RectFSpringAnim.OnUpdateListener {
|
||||
private final RemoteAnimationTargetCompat[] mAppTargets;
|
||||
private final Matrix mMatrix = new Matrix();
|
||||
private final Point mTmpPos = new Point();
|
||||
private final Rect mCurrentRect = new Rect();
|
||||
private final float mStartRadius;
|
||||
private final float mEndRadius;
|
||||
private final SurfaceTransactionApplier mSurfaceApplier;
|
||||
private final Rect mWindowTargetBounds = new Rect();
|
||||
|
||||
private final Rect mTmpRect = new Rect();
|
||||
|
||||
SpringAnimRunner(RemoteAnimationTargetCompat[] appTargets, RectF targetRect,
|
||||
Rect windowTargetBounds) {
|
||||
mAppTargets = appTargets;
|
||||
mStartRadius = QuickStepContract.getWindowCornerRadius(mLauncher);
|
||||
mEndRadius = Math.max(1, targetRect.width()) / 2f;
|
||||
mSurfaceApplier = new SurfaceTransactionApplier(mDragLayer);
|
||||
mWindowTargetBounds.set(windowTargetBounds);
|
||||
}
|
||||
|
||||
public float getCornerRadius(float progress) {
|
||||
return Utilities.mapRange(progress, mStartRadius, mEndRadius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(RectF currentRectF, float progress) {
|
||||
SurfaceParams[] params = new SurfaceParams[mAppTargets.length];
|
||||
for (int i = mAppTargets.length - 1; i >= 0; i--) {
|
||||
RemoteAnimationTargetCompat target = mAppTargets[i];
|
||||
SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash);
|
||||
|
||||
if (target.localBounds != null) {
|
||||
mTmpPos.set(target.localBounds.left, target.localBounds.top);
|
||||
} else {
|
||||
mTmpPos.set(target.position.x, target.position.y);
|
||||
}
|
||||
|
||||
if (target.mode == MODE_CLOSING) {
|
||||
currentRectF.round(mCurrentRect);
|
||||
|
||||
// Scale the target window to match the currentRectF.
|
||||
final float scale;
|
||||
|
||||
// We need to infer the crop (we crop the window to match the currentRectF).
|
||||
if (mWindowTargetBounds.height() > mWindowTargetBounds.width()) {
|
||||
scale = Math.min(1f, currentRectF.width() / mWindowTargetBounds.width());
|
||||
|
||||
int unscaledHeight = (int) (mCurrentRect.height() * (1f / scale));
|
||||
int croppedHeight = mWindowTargetBounds.height() - unscaledHeight;
|
||||
mTmpRect.set(0, 0, mWindowTargetBounds.width(),
|
||||
mWindowTargetBounds.height() - croppedHeight);
|
||||
} else {
|
||||
scale = Math.min(1f, currentRectF.height() / mWindowTargetBounds.height());
|
||||
|
||||
int unscaledWidth = (int) (mCurrentRect.width() * (1f / scale));
|
||||
int croppedWidth = mWindowTargetBounds.width() - unscaledWidth;
|
||||
mTmpRect.set(0, 0, mWindowTargetBounds.width() - croppedWidth,
|
||||
mWindowTargetBounds.height());
|
||||
}
|
||||
|
||||
// Match size and position of currentRect.
|
||||
mMatrix.setScale(scale, scale);
|
||||
mMatrix.postTranslate(mCurrentRect.left, mCurrentRect.top);
|
||||
|
||||
builder.withMatrix(mMatrix)
|
||||
.withWindowCrop(mTmpRect)
|
||||
.withAlpha(getWindowAlpha(progress))
|
||||
.withCornerRadius(getCornerRadius(progress) / scale);
|
||||
} else if (target.mode == MODE_OPENING) {
|
||||
mMatrix.setTranslate(mTmpPos.x, mTmpPos.y);
|
||||
builder.withMatrix(mMatrix)
|
||||
.withAlpha(1f);
|
||||
}
|
||||
params[i] = builder.build();
|
||||
}
|
||||
mSurfaceApplier.scheduleApply(params);
|
||||
}
|
||||
|
||||
protected float getWindowAlpha(float progress) {
|
||||
// Alpha interpolates between [1, 0] between progress values [start, end]
|
||||
final float start = 0f;
|
||||
final float end = 0.85f;
|
||||
|
||||
if (progress <= start) {
|
||||
return 1f;
|
||||
}
|
||||
if (progress >= end) {
|
||||
return 0f;
|
||||
}
|
||||
return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,8 +282,7 @@ public class AppsDividerView extends View implements StateListener<LauncherState
|
||||
|
||||
@Override
|
||||
public void setInsets(Rect insets, DeviceProfile grid) {
|
||||
int leftRightPadding = grid.desiredWorkspaceLeftRightMarginPx
|
||||
+ grid.cellLayoutPaddingLeftRightPx;
|
||||
int leftRightPadding = grid.allAppsLeftRightPadding;
|
||||
setPadding(leftRightPadding, getPaddingTop(), leftRightPadding, getPaddingBottom());
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ import com.android.launcher3.touch.ItemLongClickListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
public class PredictionRowView extends LinearLayout implements
|
||||
@@ -170,10 +171,9 @@ public class PredictionRowView extends LinearLayout implements
|
||||
private void applyPredictedApps(List<ItemInfo> items) {
|
||||
mPendingPredictedItems = null;
|
||||
mPredictedApps.clear();
|
||||
items.stream()
|
||||
mPredictedApps.addAll(items.stream()
|
||||
.filter(itemInfo -> itemInfo instanceof WorkspaceItemInfo)
|
||||
.map(itemInfo -> (WorkspaceItemInfo) itemInfo)
|
||||
.forEach(mPredictedApps::add);
|
||||
.map(itemInfo -> (WorkspaceItemInfo) itemInfo).collect(Collectors.toList()));
|
||||
applyPredictionApps();
|
||||
}
|
||||
|
||||
@@ -249,8 +249,7 @@ public class PredictionRowView extends LinearLayout implements
|
||||
|
||||
@Override
|
||||
public void setInsets(Rect insets, DeviceProfile grid) {
|
||||
int leftRightPadding = grid.desiredWorkspaceLeftRightMarginPx
|
||||
+ grid.cellLayoutPaddingLeftRightPx;
|
||||
int leftRightPadding = grid.allAppsLeftRightPadding;
|
||||
setPadding(leftRightPadding, getPaddingTop(), leftRightPadding, getPaddingBottom());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.hybridhotseat;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.launcher3.BaseActivity;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.launcher3.util.ActivityTracker;
|
||||
|
||||
/**
|
||||
* Proxy activity to return user to home screen and show halfsheet education
|
||||
*/
|
||||
public class HotseatEduActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent homeIntent = new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_HOME)
|
||||
.setPackage(getPackageName())
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
Launcher.ACTIVITY_TRACKER.registerCallback(new HotseatActivityTracker());
|
||||
startActivity(homeIntent);
|
||||
finish();
|
||||
}
|
||||
|
||||
static class HotseatActivityTracker<T extends QuickstepLauncher> implements
|
||||
ActivityTracker.SchedulerCallback {
|
||||
|
||||
@Override
|
||||
public boolean init(BaseActivity activity, boolean alreadyOnHome) {
|
||||
QuickstepLauncher launcher = (QuickstepLauncher) activity;
|
||||
if (launcher != null) {
|
||||
launcher.getHotseatPredictionController().showEdu();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -15,18 +15,22 @@
|
||||
*/
|
||||
package com.android.launcher3.hybridhotseat;
|
||||
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent
|
||||
.LAUNCHER_HOTSEAT_EDU_ONLY_TIP;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_ONLY_TIP;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Rect;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.CellLayout;
|
||||
import com.android.launcher3.Hotseat;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.model.data.FolderInfo;
|
||||
@@ -47,6 +51,8 @@ import java.util.stream.IntStream;
|
||||
*/
|
||||
public class HotseatEduController {
|
||||
|
||||
private static final String TAG = "HotseatEduController";
|
||||
|
||||
public static final String SETTINGS_ACTION =
|
||||
"android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS";
|
||||
|
||||
@@ -188,8 +194,12 @@ public class HotseatEduController {
|
||||
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
|
||||
mNewScreens = IntArray.wrap(pageId);
|
||||
}
|
||||
for (int i = 0; i < mLauncher.getDeviceProfile().numShownHotseatIcons; i++) {
|
||||
View child = mHotseat.getChildAt(i, 0);
|
||||
boolean isPortrait = !mLauncher.getDeviceProfile().isVerticalBarLayout();
|
||||
int hotseatItemsNum = mLauncher.getDeviceProfile().numShownHotseatIcons;
|
||||
for (int i = 0; i < hotseatItemsNum; i++) {
|
||||
int x = isPortrait ? i : 0;
|
||||
int y = isPortrait ? 0 : hotseatItemsNum - i - 1;
|
||||
View child = mHotseat.getChildAt(x, y);
|
||||
if (child == null || child.getTag() == null) continue;
|
||||
ItemInfo tag = (ItemInfo) child.getTag();
|
||||
if (tag.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) continue;
|
||||
@@ -229,8 +239,7 @@ public class HotseatEduController {
|
||||
R.string.hotseat_prediction_settings, null,
|
||||
() -> mLauncher.startActivity(getSettingsIntent()));
|
||||
} else {
|
||||
new ArrowTipView(mLauncher).show(
|
||||
mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
|
||||
showHotseatArrowTip(true, mLauncher.getString(R.string.hotseat_tip_no_empty_slots));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,15 +260,50 @@ public class HotseatEduController {
|
||||
if (requiresMigration && canMigrateToFirstPage) {
|
||||
showDialog();
|
||||
} else {
|
||||
new ArrowTipView(mLauncher).show(mLauncher.getString(
|
||||
if (showHotseatArrowTip(requiresMigration, mLauncher.getString(
|
||||
requiresMigration ? R.string.hotseat_tip_no_empty_slots
|
||||
: R.string.hotseat_auto_enrolled),
|
||||
mHotseat.getTop());
|
||||
mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_ONLY_TIP);
|
||||
: R.string.hotseat_auto_enrolled))) {
|
||||
mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_ONLY_TIP);
|
||||
}
|
||||
finishOnboarding();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a child suitable child in hotseat and shows arrow tip pointing at it.
|
||||
*
|
||||
* @param usePinned used to determine target view. If true, will use the first matching pinned
|
||||
* item. Otherwise, will use the first predicted child
|
||||
* @param message String to be shown inside the arrowView
|
||||
* @return whether suitable child was found and tip was shown
|
||||
*/
|
||||
private boolean showHotseatArrowTip(boolean usePinned, String message) {
|
||||
int childCount = mHotseat.getShortcutsAndWidgets().getChildCount();
|
||||
boolean isPortrait = !mLauncher.getDeviceProfile().isVerticalBarLayout();
|
||||
|
||||
BubbleTextView tipTargetView = null;
|
||||
for (int i = childCount - 1; i > -1; i--) {
|
||||
int x = isPortrait ? i : 0;
|
||||
int y = isPortrait ? 0 : i;
|
||||
View v = mHotseat.getShortcutsAndWidgets().getChildAt(x, y);
|
||||
if (v instanceof BubbleTextView && v.getTag() instanceof WorkspaceItemInfo) {
|
||||
ItemInfo info = (ItemInfo) v.getTag();
|
||||
boolean isPinned = info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
|
||||
if (isPinned == usePinned) {
|
||||
tipTargetView = (BubbleTextView) v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tipTargetView == null) {
|
||||
Log.e(TAG, "Unable to find suitable view for ArrowTip");
|
||||
return false;
|
||||
}
|
||||
Rect bounds = Utilities.getViewBounds(tipTargetView);
|
||||
new ArrowTipView(mLauncher).show(message, Gravity.END, bounds.centerX(), bounds.top);
|
||||
return true;
|
||||
}
|
||||
|
||||
void showDialog() {
|
||||
if (mPredictedApps == null || mPredictedApps.isEmpty()) {
|
||||
return;
|
||||
|
||||
@@ -28,17 +28,20 @@ import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.CellLayout;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Insettable;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.uioverrides.ApiWrapper;
|
||||
import com.android.launcher3.uioverrides.PredictedAppIcon;
|
||||
import com.android.launcher3.views.AbstractSlideInView;
|
||||
|
||||
@@ -77,6 +80,11 @@ public class HotseatEduDialog extends AbstractSlideInView<Launcher> implements I
|
||||
mContent = this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
super.onLayout(changed, l, t, r, b);
|
||||
setTranslationShift(TRANSLATION_SHIFT_CLOSED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
@@ -84,8 +92,9 @@ public class HotseatEduDialog extends AbstractSlideInView<Launcher> implements I
|
||||
mHotseatWrapper = findViewById(R.id.hotseat_wrapper);
|
||||
mSampleHotseat = findViewById(R.id.sample_prediction);
|
||||
|
||||
Context context = getContext();
|
||||
DeviceProfile grid = mActivityContext.getDeviceProfile();
|
||||
Rect padding = grid.getHotseatLayoutPadding();
|
||||
Rect padding = grid.getHotseatLayoutPadding(context);
|
||||
|
||||
mSampleHotseat.getLayoutParams().height = grid.cellHeightPx;
|
||||
mSampleHotseat.setGridSize(grid.numShownHotseatIcons, 1);
|
||||
@@ -97,6 +106,15 @@ public class HotseatEduDialog extends AbstractSlideInView<Launcher> implements I
|
||||
mDismissBtn = findViewById(R.id.no_thanks);
|
||||
mDismissBtn.setOnClickListener(this::onDismiss);
|
||||
|
||||
LinearLayout buttonContainer = findViewById(R.id.button_container);
|
||||
int adjustedMarginEnd = ApiWrapper.getHotseatEndOffset(context)
|
||||
- buttonContainer.getPaddingEnd();
|
||||
if (InvariantDeviceProfile.INSTANCE.get(context)
|
||||
.getDeviceProfile(context).isTaskbarPresent && adjustedMarginEnd > 0) {
|
||||
((LinearLayout.LayoutParams) buttonContainer.getLayoutParams()).setMarginEnd(
|
||||
adjustedMarginEnd);
|
||||
}
|
||||
|
||||
// update ui to reflect which migration method is going to be used
|
||||
if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
|
||||
((TextView) findViewById(R.id.hotseat_edu_content)).setText(
|
||||
@@ -200,9 +218,9 @@ public class HotseatEduDialog extends AbstractSlideInView<Launcher> implements I
|
||||
}
|
||||
AbstractFloatingView.closeAllOpenViews(mActivityContext);
|
||||
attachToContainer();
|
||||
mActivityContext.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_SEEN);
|
||||
animateOpen();
|
||||
populatePreview(predictions);
|
||||
mActivityContext.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_SEEN);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+34
-34
@@ -55,8 +55,8 @@ import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.touch.ItemLongClickListener;
|
||||
import com.android.launcher3.uioverrides.PredictedAppIcon;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.launcher3.util.ItemInfoMatcher;
|
||||
import com.android.launcher3.util.OnboardingPrefs;
|
||||
import com.android.launcher3.views.ArrowTipView;
|
||||
import com.android.launcher3.views.Snackbar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -152,37 +152,14 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
*/
|
||||
public void showEdu() {
|
||||
mLauncher.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> {
|
||||
if (mPredictedItems.isEmpty()) {
|
||||
// launcher has empty predictions set
|
||||
Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_disabled,
|
||||
R.string.hotseat_prediction_settings, null,
|
||||
() -> mLauncher.startActivity(getSettingsIntent()));
|
||||
} else if (getPredictedIcons().size() >= (mHotSeatItemsCount + 1) / 2) {
|
||||
showDiscoveryTip();
|
||||
} else {
|
||||
HotseatEduController eduController = new HotseatEduController(mLauncher);
|
||||
eduController.setPredictedApps(mPredictedItems.stream()
|
||||
.map(i -> (WorkspaceItemInfo) i)
|
||||
.collect(Collectors.toList()));
|
||||
eduController.showEdu();
|
||||
}
|
||||
HotseatEduController eduController = new HotseatEduController(mLauncher);
|
||||
eduController.setPredictedApps(mPredictedItems.stream()
|
||||
.map(i -> (WorkspaceItemInfo) i)
|
||||
.collect(Collectors.toList()));
|
||||
eduController.showEdu();
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows educational tip for hotseat if user does not go through Tips app.
|
||||
*/
|
||||
private void showDiscoveryTip() {
|
||||
if (getPredictedIcons().isEmpty()) {
|
||||
new ArrowTipView(mLauncher).show(
|
||||
mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
|
||||
} else {
|
||||
Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
|
||||
R.string.hotseat_prediction_settings, null,
|
||||
() -> mLauncher.startActivity(getSettingsIntent()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if hotseat client has predictions
|
||||
*/
|
||||
@@ -200,6 +177,7 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
}
|
||||
|
||||
int predictionIndex = 0;
|
||||
int numViewsAnimated = 0;
|
||||
ArrayList<WorkspaceItemInfo> newItems = new ArrayList<>();
|
||||
// make sure predicted icon removal and filling predictions don't step on each other
|
||||
if (mIconRemoveAnimators != null && mIconRemoveAnimators.isRunning()) {
|
||||
@@ -233,7 +211,11 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
(WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
|
||||
if (isPredictedIcon(child) && child.isEnabled()) {
|
||||
PredictedAppIcon icon = (PredictedAppIcon) child;
|
||||
icon.applyFromWorkspaceItem(predictedItem);
|
||||
boolean animateIconChange = icon.shouldAnimateIconChange(predictedItem);
|
||||
icon.applyFromWorkspaceItem(predictedItem, animateIconChange, numViewsAnimated);
|
||||
if (animateIconChange) {
|
||||
numViewsAnimated++;
|
||||
}
|
||||
icon.finishBinding(mPredictionLongClickListener);
|
||||
} else {
|
||||
newItems.add(predictedItem);
|
||||
@@ -262,10 +244,6 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
} else {
|
||||
removeOutlineDrawings();
|
||||
}
|
||||
|
||||
if (mLauncher.getTaskbarUIController() != null) {
|
||||
mLauncher.getTaskbarUIController().onHotseatUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
private void removeOutlineDrawings() {
|
||||
@@ -496,6 +474,28 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
.log(LAUNCHER_HOTSEAT_RANKED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when app/shortcut icon is removed by system. This is used to prune visible stale
|
||||
* predictions while while waiting for AppAPrediction service to send new batch of predictions.
|
||||
*
|
||||
* @param matcher filter matching items that have been removed
|
||||
*/
|
||||
public void onModelItemsRemoved(ItemInfoMatcher matcher) {
|
||||
if (mPredictedItems.removeIf(matcher::matchesInfo)) {
|
||||
fillGapsWithPrediction(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when user completes adding item requiring a config activity to the hotseat
|
||||
*/
|
||||
public void onDeferredDrop(int cellX, int cellY) {
|
||||
View child = mHotseat.getChildAt(cellX, cellY);
|
||||
if (child instanceof PredictedAppIcon) {
|
||||
removeIconWithoutNotify((PredictedAppIcon) child);
|
||||
}
|
||||
}
|
||||
|
||||
private class PinPrediction extends SystemShortcut<QuickstepLauncher> {
|
||||
|
||||
private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
|
||||
|
||||
@@ -16,33 +16,25 @@
|
||||
package com.android.launcher3.hybridhotseat;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
|
||||
import static com.android.launcher3.model.PredictionHelper.getAppTargetFromItemInfo;
|
||||
import static com.android.launcher3.model.PredictionHelper.isTrackedForHotseatPrediction;
|
||||
import static com.android.launcher3.model.PredictionHelper.wrapAppTargetWithItemLocation;
|
||||
|
||||
import android.app.prediction.AppTarget;
|
||||
import android.app.prediction.AppTargetEvent;
|
||||
import android.app.prediction.AppTargetId;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.model.BgDataModel;
|
||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.shortcuts.ShortcutKey;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Model helper for app predictions in workspace
|
||||
*/
|
||||
public class HotseatPredictionModel {
|
||||
private static final String APP_LOCATION_HOTSEAT = "hotseat";
|
||||
private static final String APP_LOCATION_WORKSPACE = "workspace";
|
||||
|
||||
private static final String BUNDLE_KEY_PIN_EVENTS = "pin_events";
|
||||
private static final String BUNDLE_KEY_CURRENT_ITEMS = "current_items";
|
||||
|
||||
@@ -54,15 +46,15 @@ public class HotseatPredictionModel {
|
||||
ArrayList<AppTargetEvent> events = new ArrayList<>();
|
||||
ArrayList<ItemInfo> workspaceItems = dataModel.getAllWorkspaceItems();
|
||||
for (ItemInfo item : workspaceItems) {
|
||||
AppTarget target = getAppTargetFromInfo(context, item);
|
||||
if (target != null && !isTrackedForPrediction(item)) continue;
|
||||
events.add(wrapAppTargetWithLocation(target, AppTargetEvent.ACTION_PIN, item));
|
||||
AppTarget target = getAppTargetFromItemInfo(context, item);
|
||||
if (target != null && !isTrackedForHotseatPrediction(item)) continue;
|
||||
events.add(wrapAppTargetWithItemLocation(target, AppTargetEvent.ACTION_PIN, item));
|
||||
}
|
||||
ArrayList<AppTarget> currentTargets = new ArrayList<>();
|
||||
FixedContainerItems hotseatItems = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
|
||||
if (hotseatItems != null) {
|
||||
for (ItemInfo itemInfo : hotseatItems.items) {
|
||||
AppTarget target = getAppTargetFromInfo(context, itemInfo);
|
||||
AppTarget target = getAppTargetFromItemInfo(context, itemInfo);
|
||||
if (target != null) currentTargets.add(target);
|
||||
}
|
||||
}
|
||||
@@ -70,56 +62,4 @@ public class HotseatPredictionModel {
|
||||
bundle.putParcelableArrayList(BUNDLE_KEY_CURRENT_ITEMS, currentTargets);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns for {@link AppTarget} object given an {@link ItemInfo}. Returns null
|
||||
* if item is not supported prediction
|
||||
*/
|
||||
public static AppTarget getAppTargetFromInfo(Context context, ItemInfo info) {
|
||||
if (info == null) return null;
|
||||
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
|
||||
&& info instanceof LauncherAppWidgetInfo
|
||||
&& ((LauncherAppWidgetInfo) info).providerName != null) {
|
||||
ComponentName cn = ((LauncherAppWidgetInfo) info).providerName;
|
||||
return new AppTarget.Builder(new AppTargetId("widget:" + cn.getPackageName()),
|
||||
cn.getPackageName(), info.user).setClassName(cn.getClassName()).build();
|
||||
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
|
||||
&& info.getTargetComponent() != null) {
|
||||
ComponentName cn = info.getTargetComponent();
|
||||
return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()),
|
||||
cn.getPackageName(), info.user).setClassName(cn.getClassName()).build();
|
||||
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
|
||||
&& info instanceof WorkspaceItemInfo) {
|
||||
ShortcutKey shortcutKey = ShortcutKey.fromItemInfo(info);
|
||||
//TODO: switch to using full shortcut info
|
||||
return new AppTarget.Builder(new AppTargetId("shortcut:" + shortcutKey.getId()),
|
||||
shortcutKey.componentName.getPackageName(), shortcutKey.user).build();
|
||||
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
|
||||
return new AppTarget.Builder(new AppTargetId("folder:" + info.id),
|
||||
context.getPackageName(), info.user).build();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns {@link AppTargetEvent} from an {@link AppTarget}, action, and item
|
||||
* location using {@link ItemInfo}
|
||||
*/
|
||||
public static AppTargetEvent wrapAppTargetWithLocation(
|
||||
AppTarget target, int action, ItemInfo info) {
|
||||
String location = String.format(Locale.ENGLISH, "%s/%d/[%d,%d]/[%d,%d]",
|
||||
info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
|
||||
? APP_LOCATION_HOTSEAT : APP_LOCATION_WORKSPACE,
|
||||
info.screenId, info.cellX, info.cellY, info.spanX, info.spanY);
|
||||
return new AppTargetEvent.Builder(target, action).setLaunchLocation(location).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine if {@link ItemInfo} should be tracked and reported to predictors
|
||||
*/
|
||||
public static boolean isTrackedForPrediction(ItemInfo info) {
|
||||
return info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT || (
|
||||
info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP
|
||||
&& info.screenId == Workspace.FIRST_SCREEN_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import static android.app.prediction.AppTargetEvent.ACTION_UNPIN;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED;
|
||||
@@ -30,10 +31,13 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_REMOVE;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_FOLDER_CREATED;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONRESUME;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
|
||||
import static com.android.launcher3.model.PredictionHelper.isTrackedForHotseatPrediction;
|
||||
import static com.android.launcher3.model.PredictionHelper.isTrackedForWidgetPrediction;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
@@ -61,7 +65,6 @@ import com.android.launcher3.logger.LauncherAtom.FolderContainer;
|
||||
import com.android.launcher3.logger.LauncherAtom.HotseatContainer;
|
||||
import com.android.launcher3.logger.LauncherAtom.WorkspaceContainer;
|
||||
import com.android.launcher3.logging.StatsLogManager.EventEnum;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
import com.android.launcher3.shortcuts.ShortcutRequest;
|
||||
import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer;
|
||||
@@ -140,6 +143,9 @@ public class AppEventProducer implements StatsLogConsumer {
|
||||
if (isTrackedForHotseatPrediction(mLastDragItem)) {
|
||||
sendEvent(mLastDragItem, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION);
|
||||
}
|
||||
if (isTrackedForWidgetPrediction(atomInfo)) {
|
||||
sendEvent(atomInfo, ACTION_PIN, CONTAINER_WIDGETS_PREDICTION);
|
||||
}
|
||||
mLastDragItem = null;
|
||||
} else if (event == LAUNCHER_ITEM_DROP_FOLDER_CREATED) {
|
||||
if (isTrackedForHotseatPrediction(atomInfo)) {
|
||||
@@ -157,10 +163,18 @@ public class AppEventProducer implements StatsLogConsumer {
|
||||
if (mLastDragItem != null && isTrackedForHotseatPrediction(mLastDragItem)) {
|
||||
sendEvent(mLastDragItem, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION);
|
||||
}
|
||||
if (mLastDragItem != null && isTrackedForWidgetPrediction(mLastDragItem)) {
|
||||
sendEvent(mLastDragItem, ACTION_UNPIN, CONTAINER_WIDGETS_PREDICTION);
|
||||
}
|
||||
} else if (event == LAUNCHER_HOTSEAT_PREDICTION_PINNED) {
|
||||
if (isTrackedForHotseatPrediction(atomInfo)) {
|
||||
sendEvent(atomInfo, ACTION_PIN, CONTAINER_HOTSEAT_PREDICTION);
|
||||
}
|
||||
} else if (event == LAUNCHER_ONRESUME) {
|
||||
AppTarget target = new AppTarget.Builder(new AppTargetId("launcher:launcher"),
|
||||
mContext.getPackageName(), Process.myUserHandle())
|
||||
.build();
|
||||
sendEvent(target, atomInfo, ACTION_LAUNCH, CONTAINER_PREDICTION);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,9 +271,6 @@ public class AppEventProducer implements StatsLogConsumer {
|
||||
case ALL_APPS_CONTAINER: {
|
||||
return "all-apps";
|
||||
}
|
||||
case SEARCH_RESULT_CONTAINER: {
|
||||
return "search-results";
|
||||
}
|
||||
case PREDICTED_HOTSEAT_CONTAINER: {
|
||||
return "predictions/hotseat";
|
||||
}
|
||||
@@ -279,6 +290,16 @@ public class AppEventProducer implements StatsLogConsumer {
|
||||
}
|
||||
return "folder";
|
||||
}
|
||||
case SEARCH_RESULT_CONTAINER:
|
||||
return "search-results";
|
||||
case EXTENDED_CONTAINERS: {
|
||||
switch(ci.getExtendedContainers().getContainerCase()) {
|
||||
case DEVICE_SEARCH_RESULT_CONTAINER:
|
||||
case CORRECTED_DEVICE_SEARCH_RESULT_CONTAINER:
|
||||
return "search-results";
|
||||
}
|
||||
}
|
||||
default: // fall out
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -296,19 +317,4 @@ public class AppEventProducer implements StatsLogConsumer {
|
||||
return TextUtils.isEmpty(componentNameString)
|
||||
? null : ComponentName.unflattenFromString(componentNameString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine if {@link ItemInfo} should be tracked and reported to predictors
|
||||
*/
|
||||
private static boolean isTrackedForHotseatPrediction(LauncherAtom.ItemInfo info) {
|
||||
ContainerInfo ci = info.getContainerInfo();
|
||||
switch (ci.getContainerCase()) {
|
||||
case HOTSEAT:
|
||||
return true;
|
||||
case WORKSPACE:
|
||||
return ci.getWorkspace().getPageIndex() == 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.WORKSPACE;
|
||||
|
||||
import android.app.prediction.AppTarget;
|
||||
import android.app.prediction.AppTargetEvent;
|
||||
import android.app.prediction.AppTargetId;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.logger.LauncherAtom;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.shortcuts.ShortcutKey;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/** Helper class with methods for converting launcher items to form usable by predictors */
|
||||
public final class PredictionHelper {
|
||||
private static final String APP_LOCATION_HOTSEAT = "hotseat";
|
||||
private static final String APP_LOCATION_WORKSPACE = "workspace";
|
||||
|
||||
/**
|
||||
* Creates and returns an {@link AppTarget} object for an {@link ItemInfo}. Returns null
|
||||
* if item type is not supported in predictions
|
||||
*/
|
||||
@Nullable
|
||||
public static AppTarget getAppTargetFromItemInfo(Context context, ItemInfo info) {
|
||||
if (info == null) return null;
|
||||
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
|
||||
&& info instanceof LauncherAppWidgetInfo
|
||||
&& ((LauncherAppWidgetInfo) info).providerName != null) {
|
||||
ComponentName cn = ((LauncherAppWidgetInfo) info).providerName;
|
||||
return new AppTarget.Builder(new AppTargetId("widget:" + cn.getPackageName()),
|
||||
cn.getPackageName(), info.user).setClassName(cn.getClassName()).build();
|
||||
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
|
||||
&& info.getTargetComponent() != null) {
|
||||
ComponentName cn = info.getTargetComponent();
|
||||
return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()),
|
||||
cn.getPackageName(), info.user).setClassName(cn.getClassName()).build();
|
||||
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
|
||||
&& info instanceof WorkspaceItemInfo) {
|
||||
ShortcutKey shortcutKey = ShortcutKey.fromItemInfo(info);
|
||||
//TODO: switch to using full shortcut info
|
||||
return new AppTarget.Builder(new AppTargetId("shortcut:" + shortcutKey.getId()),
|
||||
shortcutKey.componentName.getPackageName(), shortcutKey.user).build();
|
||||
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
|
||||
return new AppTarget.Builder(new AppTargetId("folder:" + info.id),
|
||||
context.getPackageName(), info.user).build();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns {@link AppTargetEvent} from an {@link AppTarget}, action, and item
|
||||
* location using {@link ItemInfo}
|
||||
*/
|
||||
public static AppTargetEvent wrapAppTargetWithItemLocation(
|
||||
AppTarget target, int action, ItemInfo info) {
|
||||
String location = String.format(Locale.ENGLISH, "%s/%d/[%d,%d]/[%d,%d]",
|
||||
info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
|
||||
? APP_LOCATION_HOTSEAT : APP_LOCATION_WORKSPACE,
|
||||
info.screenId, info.cellX, info.cellY, info.spanX, info.spanY);
|
||||
return new AppTargetEvent.Builder(target, action).setLaunchLocation(location).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine if {@link ItemInfo} should be tracked and reported to hotseat
|
||||
* predictors
|
||||
*/
|
||||
public static boolean isTrackedForHotseatPrediction(ItemInfo info) {
|
||||
return info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT || (
|
||||
info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP
|
||||
&& info.screenId == Workspace.FIRST_SCREEN_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine if {@link LauncherAtom.ItemInfo} should be tracked and reported to
|
||||
* hotseat predictors
|
||||
*/
|
||||
public static boolean isTrackedForHotseatPrediction(LauncherAtom.ItemInfo info) {
|
||||
LauncherAtom.ContainerInfo ci = info.getContainerInfo();
|
||||
switch (ci.getContainerCase()) {
|
||||
case HOTSEAT:
|
||||
return true;
|
||||
case WORKSPACE:
|
||||
return ci.getWorkspace().getPageIndex() == 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine if {@link ItemInfo} should be tracked and reported to widget
|
||||
* predictors
|
||||
*/
|
||||
public static boolean isTrackedForWidgetPrediction(ItemInfo info) {
|
||||
return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
|
||||
&& info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine if {@link LauncherAtom.ItemInfo} should be tracked and reported
|
||||
* to widget predictors
|
||||
*/
|
||||
public static boolean isTrackedForWidgetPrediction(LauncherAtom.ItemInfo info) {
|
||||
return info.getItemCase() == LauncherAtom.ItemInfo.ItemCase.WIDGET
|
||||
&& info.getContainerInfo().getContainerCase() == WORKSPACE;
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,14 @@ import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICA
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
|
||||
import static com.android.launcher3.Utilities.getDevicePrefs;
|
||||
import static com.android.launcher3.hybridhotseat.HotseatPredictionModel.convertDataModelToAppTargetBundle;
|
||||
import static com.android.launcher3.model.PredictionHelper.getAppTargetFromItemInfo;
|
||||
import static com.android.launcher3.model.PredictionHelper.wrapAppTargetWithItemLocation;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
|
||||
import android.Manifest;
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
|
||||
import android.app.StatsManager;
|
||||
import android.app.prediction.AppPredictionContext;
|
||||
import android.app.prediction.AppPredictionManager;
|
||||
import android.app.prediction.AppPredictor;
|
||||
@@ -39,8 +45,10 @@ import android.content.pm.LauncherActivityInfo;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.util.StatsEvent;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -48,9 +56,9 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.logger.LauncherAtom;
|
||||
import com.android.launcher3.logging.InstanceId;
|
||||
import com.android.launcher3.logging.InstanceIdSequence;
|
||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||
@@ -59,11 +67,13 @@ import com.android.launcher3.model.data.FolderInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.shortcuts.ShortcutKey;
|
||||
import com.android.launcher3.util.Executors;
|
||||
import com.android.launcher3.util.IntSparseArrayMap;
|
||||
import com.android.launcher3.util.PersistedItemArray;
|
||||
import com.android.quickstep.logging.SettingsChangeLogger;
|
||||
import com.android.quickstep.logging.StatsLogCompatManager;
|
||||
import com.android.systemui.shared.system.SysUiStatsLog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -73,10 +83,11 @@ import java.util.stream.IntStream;
|
||||
/**
|
||||
* Model delegate which loads prediction items
|
||||
*/
|
||||
public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChangeListener {
|
||||
public class QuickstepModelDelegate extends ModelDelegate {
|
||||
|
||||
public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
|
||||
private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS";
|
||||
private static final String BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets";
|
||||
private static final int NUM_OF_RECOMMENDED_WIDGETS_PREDICATION = 20;
|
||||
|
||||
private static final boolean IS_DEBUG = false;
|
||||
@@ -91,15 +102,18 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
|
||||
|
||||
private final InvariantDeviceProfile mIDP;
|
||||
private final AppEventProducer mAppEventProducer;
|
||||
private final StatsManager mStatsManager;
|
||||
private final Context mContext;
|
||||
|
||||
protected boolean mActive = false;
|
||||
|
||||
public QuickstepModelDelegate(Context context) {
|
||||
mContext = context;
|
||||
mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent);
|
||||
|
||||
mIDP = InvariantDeviceProfile.INSTANCE.get(context);
|
||||
mIDP.addOnChangeListener(this);
|
||||
StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer);
|
||||
mStatsManager = context.getSystemService(StatsManager.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -155,17 +169,82 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
|
||||
}
|
||||
InstanceId instanceId = new InstanceIdSequence().newInstanceId();
|
||||
for (ItemInfo info : itemsIdMap) {
|
||||
FolderInfo parent = info.container > 0
|
||||
? (FolderInfo) itemsIdMap.get(info.container) : null;
|
||||
FolderInfo parent = getContainer(info, itemsIdMap);
|
||||
StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId);
|
||||
}
|
||||
additionalSnapshotEvents(instanceId);
|
||||
prefs.edit().putLong(LAST_SNAPSHOT_TIME_MILLIS, now).apply();
|
||||
}
|
||||
|
||||
// Only register for launcher snapshot logging if this is the primary ModelDelegate
|
||||
// instance, as there will be additional instances that may be destroyed at any time.
|
||||
if (mIsPrimaryInstance) {
|
||||
registerSnapshotLoggingCallback();
|
||||
}
|
||||
}
|
||||
|
||||
protected void additionalSnapshotEvents(InstanceId snapshotInstanceId){}
|
||||
|
||||
/**
|
||||
* Registers a callback to log launcher workspace layout using Statsd pulled atom.
|
||||
*/
|
||||
protected void registerSnapshotLoggingCallback() {
|
||||
if (mStatsManager == null) {
|
||||
Log.d(TAG, "Failed to get StatsManager");
|
||||
}
|
||||
|
||||
try {
|
||||
mStatsManager.setPullAtomCallback(
|
||||
SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT,
|
||||
null /* PullAtomMetadata */,
|
||||
MODEL_EXECUTOR,
|
||||
(i, eventList) -> {
|
||||
InstanceId instanceId = new InstanceIdSequence().newInstanceId();
|
||||
IntSparseArrayMap<ItemInfo> itemsIdMap;
|
||||
synchronized (mDataModel) {
|
||||
itemsIdMap = mDataModel.itemsIdMap.clone();
|
||||
}
|
||||
|
||||
for (ItemInfo info : itemsIdMap) {
|
||||
FolderInfo parent = getContainer(info, itemsIdMap);
|
||||
LauncherAtom.ItemInfo itemInfo = info.buildProto(parent);
|
||||
Log.d(TAG, itemInfo.toString());
|
||||
StatsEvent statsEvent = StatsLogCompatManager.buildStatsEvent(itemInfo,
|
||||
instanceId);
|
||||
eventList.add(statsEvent);
|
||||
}
|
||||
Log.d(TAG,
|
||||
String.format(
|
||||
"Successfully logged %d workspace items with instanceId=%d",
|
||||
itemsIdMap.size(), instanceId.getId()));
|
||||
additionalSnapshotEvents(instanceId);
|
||||
SettingsChangeLogger.INSTANCE.get(mContext).logSnapshot(instanceId);
|
||||
return StatsManager.PULL_SUCCESS;
|
||||
}
|
||||
);
|
||||
Log.d(TAG, "Successfully registered for launcher snapshot logging!");
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "Failed to register launcher snapshot logging callback with StatsManager",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
private static FolderInfo getContainer(ItemInfo info, IntSparseArrayMap<ItemInfo> itemsIdMap) {
|
||||
if (info.container > 0) {
|
||||
ItemInfo containerInfo = itemsIdMap.get(info.container);
|
||||
|
||||
if (!(containerInfo instanceof FolderInfo)) {
|
||||
Log.e(TAG, String.format(
|
||||
"Item info: %s found with invalid container: %s",
|
||||
info,
|
||||
containerInfo));
|
||||
} else {
|
||||
return (FolderInfo) containerInfo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateData() {
|
||||
super.validateData();
|
||||
@@ -182,9 +261,10 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
|
||||
super.destroy();
|
||||
mActive = false;
|
||||
StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer);
|
||||
|
||||
if (mIsPrimaryInstance) {
|
||||
mStatsManager.clearPullAtomCallback(SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT);
|
||||
}
|
||||
destroyPredictors();
|
||||
mIDP.removeOnChangeListener(this);
|
||||
}
|
||||
|
||||
private void destroyPredictors() {
|
||||
@@ -225,6 +305,7 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
|
||||
registerWidgetsPredictor(apm.createAppPredictionSession(
|
||||
new AppPredictionContext.Builder(context)
|
||||
.setUiSurface("widgets")
|
||||
.setExtras(getBundleForWidgetsOnWorkspace(context, mDataModel))
|
||||
.setPredictedTargetCount(NUM_OF_RECOMMENDED_WIDGETS_PREDICATION)
|
||||
.build()));
|
||||
}
|
||||
@@ -232,7 +313,7 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
|
||||
private void registerPredictor(PredictorState state, AppPredictor predictor) {
|
||||
state.predictor = predictor;
|
||||
state.predictor.registerPredictionUpdates(
|
||||
Executors.MODEL_EXECUTOR, new AppPredictor.Callback() {
|
||||
MODEL_EXECUTOR, new AppPredictor.Callback() {
|
||||
@Keep
|
||||
@Override
|
||||
public void onTargetsAvailable(@NonNull List<AppTarget> targets) {
|
||||
@@ -253,7 +334,7 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
|
||||
private void registerWidgetsPredictor(AppPredictor predictor) {
|
||||
mWidgetsRecommendationState.predictor = predictor;
|
||||
mWidgetsRecommendationState.predictor.registerPredictionUpdates(
|
||||
Executors.MODEL_EXECUTOR, new AppPredictor.Callback() {
|
||||
MODEL_EXECUTOR, new AppPredictor.Callback() {
|
||||
@Keep
|
||||
@Override
|
||||
public void onTargetsAvailable(@NonNull List<AppTarget> targets) {
|
||||
@@ -268,19 +349,44 @@ public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChange
|
||||
mWidgetsRecommendationState.predictor.requestPredictionUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIdpChanged(InvariantDeviceProfile profile) {
|
||||
// Reinitialize everything
|
||||
Executors.MODEL_EXECUTOR.execute(this::recreatePredictors);
|
||||
}
|
||||
|
||||
private void onAppTargetEvent(AppTargetEvent event, int client) {
|
||||
PredictorState state = client == CONTAINER_PREDICTION ? mAllAppsState : mHotseatState;
|
||||
PredictorState state;
|
||||
switch(client) {
|
||||
case CONTAINER_PREDICTION:
|
||||
state = mAllAppsState;
|
||||
break;
|
||||
case CONTAINER_WIDGETS_PREDICTION:
|
||||
state = mWidgetsRecommendationState;
|
||||
break;
|
||||
case CONTAINER_HOTSEAT_PREDICTION:
|
||||
default:
|
||||
state = mHotseatState;
|
||||
break;
|
||||
}
|
||||
if (state.predictor != null) {
|
||||
state.predictor.notifyAppTargetEvent(event);
|
||||
Log.d(TAG, "notifyAppTargetEvent action=" + event.getAction()
|
||||
+ " launchLocation=" + event.getLaunchLocation());
|
||||
}
|
||||
}
|
||||
|
||||
private Bundle getBundleForWidgetsOnWorkspace(Context context, BgDataModel dataModel) {
|
||||
Bundle bundle = new Bundle();
|
||||
ArrayList<AppTargetEvent> widgetEvents =
|
||||
dataModel.getAllWorkspaceItems().stream()
|
||||
.filter(PredictionHelper::isTrackedForWidgetPrediction)
|
||||
.map(item -> {
|
||||
AppTarget target = getAppTargetFromItemInfo(context, item);
|
||||
if (target == null) return null;
|
||||
return wrapAppTargetWithItemLocation(
|
||||
target, AppTargetEvent.ACTION_PIN, item);
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(toCollection(ArrayList::new));
|
||||
bundle.putParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, widgetEvents);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
static class PredictorState {
|
||||
|
||||
public final FixedContainerItems items;
|
||||
|
||||
@@ -377,7 +377,7 @@ public final class WellbeingModel extends BgObjectWithLooper {
|
||||
/**
|
||||
* Shortcut factory for generating wellbeing action
|
||||
*/
|
||||
public static final SystemShortcut.Factory SHORTCUT_FACTORY =
|
||||
public static final SystemShortcut.Factory<BaseDraggingActivity> SHORTCUT_FACTORY =
|
||||
(activity, info) -> (info.getTargetComponent() == null) ? null : INSTANCE.get(activity)
|
||||
.getShortcutForApp(
|
||||
info.getTargetComponent().getPackageName(), info.user.getIdentifier(),
|
||||
|
||||
@@ -83,7 +83,7 @@ public final class WidgetsPredictionUpdateTask extends BaseModelUpdateTask {
|
||||
}
|
||||
} else {
|
||||
Map<ComponentKey, WidgetItem> widgetItems =
|
||||
allWidgets.values().stream().flatMap(List::stream)
|
||||
allWidgets.values().stream().flatMap(List::stream).distinct()
|
||||
.collect(Collectors.toMap(widget -> (ComponentKey) widget,
|
||||
widget -> widget));
|
||||
for (AppTarget app : mTargets) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.animation.ObjectAnimator;
|
||||
import android.os.IBinder;
|
||||
import android.os.SystemProperties;
|
||||
import android.util.FloatProperty;
|
||||
import android.view.AttachedSurfaceControl;
|
||||
import android.view.CrossWindowBlurListeners;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.View;
|
||||
@@ -45,6 +46,7 @@ import com.android.systemui.shared.system.WallpaperManagerCompat;
|
||||
|
||||
import app.lawnchair.preferences.BasePreferenceManager;
|
||||
import app.lawnchair.preferences.PreferenceManager;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -97,7 +99,11 @@ public class DepthController implements StateHandler<LauncherState>,
|
||||
public void onDraw() {
|
||||
View view = mLauncher.getDragLayer();
|
||||
ViewRootImpl viewRootImpl = view.getViewRootImpl();
|
||||
setSurface(viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null);
|
||||
boolean applied = setSurface(
|
||||
viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null);
|
||||
if (!applied) {
|
||||
dispatchTransactionSurface(mDepth);
|
||||
}
|
||||
view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this));
|
||||
}
|
||||
};
|
||||
@@ -110,6 +116,13 @@ public class DepthController implements StateHandler<LauncherState>,
|
||||
}
|
||||
};
|
||||
|
||||
private final Runnable mOpaquenessListener = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
dispatchTransactionSurface(mDepth);
|
||||
}
|
||||
};
|
||||
|
||||
private final Launcher mLauncher;
|
||||
/**
|
||||
* Blur radius when completely zoomed out, in pixels.
|
||||
@@ -118,19 +131,36 @@ public class DepthController implements StateHandler<LauncherState>,
|
||||
private boolean mCrossWindowBlursEnabled;
|
||||
private WallpaperManagerCompat mWallpaperManager;
|
||||
private SurfaceControl mSurface;
|
||||
/**
|
||||
* How visible the -1 overlay is, from 0 to 1.
|
||||
*/
|
||||
private float mOverlayScrollProgress;
|
||||
/**
|
||||
* Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in.
|
||||
* @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float)
|
||||
*/
|
||||
private float mDepth;
|
||||
/**
|
||||
* Last blur value, in pixels, that was applied.
|
||||
* For debugging purposes.
|
||||
*/
|
||||
private int mCurrentBlur;
|
||||
/**
|
||||
* If we're launching and app and should not be blurring the screen for performance reasons.
|
||||
*/
|
||||
private boolean mBlurDisabledForAppLaunch;
|
||||
/**
|
||||
* If we requested early wake-up offsets to SurfaceFlinger.
|
||||
*/
|
||||
private boolean mInEarlyWakeUp;
|
||||
|
||||
// Workaround for animating the depth when multiwindow mode changes.
|
||||
private boolean mIgnoreStateChangesDuringMultiWindowAnimation = false;
|
||||
|
||||
// Hints that there is potentially content behind Launcher and that we shouldn't optimize by
|
||||
// marking the launcher surface as opaque. Only used in certain Launcher states.
|
||||
private boolean mHasContentBehindLauncher;
|
||||
|
||||
private View.OnAttachStateChangeListener mOnAttachListener;
|
||||
|
||||
private BasePreferenceManager.FloatPref mDrawerOpacity;
|
||||
@@ -154,10 +184,7 @@ public class DepthController implements StateHandler<LauncherState>,
|
||||
if (windowToken != null) {
|
||||
mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth);
|
||||
}
|
||||
if (Utilities.ATLEAST_S) {
|
||||
CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
|
||||
mCrossWindowBlurListener);
|
||||
}
|
||||
onAttached();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -165,12 +192,12 @@ public class DepthController implements StateHandler<LauncherState>,
|
||||
if (Utilities.ATLEAST_S) {
|
||||
CrossWindowBlurListeners.getInstance().removeListener(mCrossWindowBlurListener);
|
||||
}
|
||||
mLauncher.getScrimView().removeOpaquenessListener(mOpaquenessListener);
|
||||
}
|
||||
};
|
||||
mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener);
|
||||
if (mLauncher.getRootView().isAttachedToWindow() && Utilities.ATLEAST_S) {
|
||||
CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
|
||||
mCrossWindowBlurListener);
|
||||
if (mLauncher.getRootView().isAttachedToWindow()) {
|
||||
onAttached();
|
||||
}
|
||||
}
|
||||
if (mDrawerOpacity == null) {
|
||||
@@ -178,6 +205,18 @@ public class DepthController implements StateHandler<LauncherState>,
|
||||
}
|
||||
}
|
||||
|
||||
private void onAttached() {
|
||||
if (Utilities.ATLEAST_S) {
|
||||
CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
|
||||
mCrossWindowBlurListener);
|
||||
}
|
||||
mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener);
|
||||
}
|
||||
|
||||
public void setHasContentBehindLauncher(boolean hasContentBehindLauncher) {
|
||||
mHasContentBehindLauncher = hasContentBehindLauncher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the underlying activity is started or not
|
||||
*/
|
||||
@@ -193,20 +232,22 @@ public class DepthController implements StateHandler<LauncherState>,
|
||||
|
||||
/**
|
||||
* Sets the specified app target surface to apply the blur to.
|
||||
* @return true when surface was valid and transaction was dispatched.
|
||||
*/
|
||||
public void setSurface(SurfaceControl surface) {
|
||||
public boolean setSurface(SurfaceControl surface) {
|
||||
// Set launcher as the SurfaceControl when we don't need an external target anymore.
|
||||
if (surface == null) {
|
||||
ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl();
|
||||
surface = viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null;
|
||||
}
|
||||
|
||||
if (mSurface != surface) {
|
||||
mSurface = surface;
|
||||
if (surface != null) {
|
||||
dispatchTransactionSurface(mDepth);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -220,6 +261,8 @@ public class DepthController implements StateHandler<LauncherState>,
|
||||
setDepth(toDepth);
|
||||
} else if (toState == LauncherState.OVERVIEW) {
|
||||
dispatchTransactionSurface(mDepth);
|
||||
} else if (toState == LauncherState.BACKGROUND_APP) {
|
||||
mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,9 +314,19 @@ public class DepthController implements StateHandler<LauncherState>,
|
||||
if (Float.compare(mDepth, depthF) == 0 && !force) {
|
||||
return;
|
||||
}
|
||||
if (dispatchTransactionSurface(depthF)) {
|
||||
mDepth = depthF;
|
||||
dispatchTransactionSurface(depthF);
|
||||
mDepth = depthF;
|
||||
}
|
||||
|
||||
public void onOverlayScrollChanged(float progress) {
|
||||
// Round out the progress to dedupe frequent, non-perceptable updates
|
||||
int progressI = (int) (progress * 256);
|
||||
float progressF = Utilities.boundToRange(progressI / 256f, 0f, 1f);
|
||||
if (Float.compare(mOverlayScrollProgress, progressF) == 0) {
|
||||
return;
|
||||
}
|
||||
mOverlayScrollProgress = progressF;
|
||||
dispatchTransactionSurface(mDepth);
|
||||
}
|
||||
|
||||
private boolean dispatchTransactionSurface(float depth) {
|
||||
@@ -282,23 +335,38 @@ public class DepthController implements StateHandler<LauncherState>,
|
||||
return false;
|
||||
}
|
||||
ensureDependencies();
|
||||
depth = Math.max(depth, mOverlayScrollProgress);
|
||||
IBinder windowToken = mLauncher.getRootView().getWindowToken();
|
||||
if (windowToken != null) {
|
||||
mWallpaperManager.setWallpaperZoomOut(windowToken, depth);
|
||||
}
|
||||
|
||||
if (supportsBlur) {
|
||||
// We cannot mark the window as opaque in overview because there will be an app window
|
||||
// below the launcher layer, and we need to draw it -- without blurs.
|
||||
boolean isOverview = mLauncher.isInState(LauncherState.OVERVIEW);
|
||||
boolean opaque = mLauncher.getScrimView().isFullyOpaque() && !isOverview;
|
||||
boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque();
|
||||
boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg;
|
||||
|
||||
int blur = opaque || isOverview || !mCrossWindowBlursEnabled
|
||||
|| mBlurDisabledForAppLaunch ? 0 : (int) (depth * mMaxBlurRadius);
|
||||
new SurfaceControl.Transaction()
|
||||
.setBackgroundBlurRadius(mSurface, blur)
|
||||
.setOpaque(mSurface, opaque)
|
||||
.apply();
|
||||
mCurrentBlur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch || hasOpaqueBg
|
||||
? 0 : (int) (depth * mMaxBlurRadius);
|
||||
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
|
||||
.setBackgroundBlurRadius(mSurface, mCurrentBlur)
|
||||
.setOpaque(mSurface, isSurfaceOpaque);
|
||||
|
||||
// Set early wake-up flags when we know we're executing an expensive operation, this way
|
||||
// SurfaceFlinger will adjust its internal offsets to avoid jank.
|
||||
boolean wantsEarlyWakeUp = depth > 0 && depth < 1;
|
||||
if (wantsEarlyWakeUp && !mInEarlyWakeUp) {
|
||||
transaction.setEarlyWakeupStart();
|
||||
mInEarlyWakeUp = true;
|
||||
} else if (!wantsEarlyWakeUp && mInEarlyWakeUp) {
|
||||
transaction.setEarlyWakeupEnd();
|
||||
mInEarlyWakeUp = false;
|
||||
}
|
||||
|
||||
AttachedSurfaceControl rootSurfaceControl =
|
||||
mLauncher.getRootView().getRootSurfaceControl();
|
||||
if (rootSurfaceControl != null) {
|
||||
rootSurfaceControl.applyTransactionOnDraw(transaction);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -319,4 +387,18 @@ public class DepthController implements StateHandler<LauncherState>,
|
||||
mwAnimation.setAutoCancel(true);
|
||||
mwAnimation.start();
|
||||
}
|
||||
|
||||
public void dump(String prefix, PrintWriter writer) {
|
||||
writer.println(prefix + this.getClass().getSimpleName());
|
||||
writer.println(prefix + "\tmMaxBlurRadius=" + mMaxBlurRadius);
|
||||
writer.println(prefix + "\tmCrossWindowBlursEnabled=" + mCrossWindowBlursEnabled);
|
||||
writer.println(prefix + "\tmSurface=" + mSurface);
|
||||
writer.println(prefix + "\tmOverlayScrollProgress=" + mOverlayScrollProgress);
|
||||
writer.println(prefix + "\tmDepth=" + mDepth);
|
||||
writer.println(prefix + "\tmCurrentBlur=" + mCurrentBlur);
|
||||
writer.println(prefix + "\tmBlurDisabledForAppLaunch=" + mBlurDisabledForAppLaunch);
|
||||
writer.println(prefix + "\tmInEarlyWakeUp=" + mInEarlyWakeUp);
|
||||
writer.println(prefix + "\tmIgnoreStateChangesDuringMultiWindowAnimation="
|
||||
+ mIgnoreStateChangesDuringMultiWindowAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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.taskbar;
|
||||
|
||||
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
|
||||
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
|
||||
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
|
||||
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
|
||||
|
||||
import android.annotation.DrawableRes;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
|
||||
|
||||
/**
|
||||
* Creates Buttons for Taskbar for 3 button nav.
|
||||
* Can add animations and state management for buttons in this class as things progress.
|
||||
*/
|
||||
public class ButtonProvider {
|
||||
|
||||
private final int mMarginLeftRight;
|
||||
private final TaskbarActivityContext mContext;
|
||||
|
||||
public ButtonProvider(TaskbarActivityContext context) {
|
||||
mContext = context;
|
||||
mMarginLeftRight = context.getResources()
|
||||
.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
|
||||
}
|
||||
|
||||
public View getBack() {
|
||||
// Back button
|
||||
return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK);
|
||||
}
|
||||
|
||||
public View getDown() {
|
||||
// Ime down button
|
||||
return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK);
|
||||
}
|
||||
|
||||
public View getHome() {
|
||||
// Home button
|
||||
return getButtonForDrawable(R.drawable.ic_sysbar_home, BUTTON_HOME);
|
||||
}
|
||||
|
||||
public View getRecents() {
|
||||
// Recents button
|
||||
return getButtonForDrawable(R.drawable.ic_sysbar_recent, BUTTON_RECENTS);
|
||||
}
|
||||
|
||||
public View getImeSwitcher() {
|
||||
// IME Switcher Button
|
||||
return getButtonForDrawable(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH);
|
||||
}
|
||||
|
||||
private View getButtonForDrawable(@DrawableRes int drawableId, @TaskbarButton int buttonType) {
|
||||
ImageView buttonView = new ImageView(mContext);
|
||||
buttonView.setImageResource(drawableId);
|
||||
buttonView.setBackgroundResource(R.drawable.taskbar_icon_click_feedback_roundrect);
|
||||
buttonView.setPadding(mMarginLeftRight, 0, mMarginLeftRight, 0);
|
||||
buttonView.setOnClickListener(view -> mContext.onNavigationButtonClick(buttonType));
|
||||
return buttonView;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
|
||||
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
|
||||
import static com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION;
|
||||
|
||||
import android.animation.Animator;
|
||||
|
||||
import com.android.launcher3.statemanager.StateManager;
|
||||
import com.android.quickstep.RecentsActivity;
|
||||
import com.android.quickstep.fallback.RecentsState;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
|
||||
/**
|
||||
* A data source which integrates with the fallback RecentsActivity instance (for 3P launchers).
|
||||
*/
|
||||
public class FallbackTaskbarUIController extends TaskbarUIController {
|
||||
|
||||
private final RecentsActivity mRecentsActivity;
|
||||
|
||||
private final StateManager.StateListener<RecentsState> mStateListener =
|
||||
new StateManager.StateListener<RecentsState>() {
|
||||
@Override
|
||||
public void onStateTransitionStart(RecentsState toState) {
|
||||
animateToRecentsState(toState);
|
||||
|
||||
// Handle tapping on live tile.
|
||||
RecentsView recentsView = mRecentsActivity.getOverviewPanel();
|
||||
recentsView.setTaskLaunchListener(toState == RecentsState.DEFAULT
|
||||
? (() -> animateToRecentsState(RecentsState.BACKGROUND_APP)) : null);
|
||||
}
|
||||
};
|
||||
|
||||
public FallbackTaskbarUIController(RecentsActivity recentsActivity) {
|
||||
mRecentsActivity = recentsActivity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init(TaskbarControllers taskbarControllers) {
|
||||
super.init(taskbarControllers);
|
||||
|
||||
mRecentsActivity.setTaskbarUIController(this);
|
||||
mRecentsActivity.getStateManager().addStateListener(mStateListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
mRecentsActivity.setTaskbarUIController(null);
|
||||
mRecentsActivity.getStateManager().removeStateListener(mStateListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an animation to animate the taskbar for the given state (but does not start it).
|
||||
* Currently this animation just force stashes the taskbar in Overview.
|
||||
*/
|
||||
public Animator createAnimToRecentsState(RecentsState toState, long duration) {
|
||||
boolean forceStashed = toState.hasOverviewActions();
|
||||
TaskbarStashController controller = mControllers.taskbarStashController;
|
||||
// Set both FLAG_IN_STASHED_LAUNCHER_STATE and FLAG_IN_APP to ensure the state is respected.
|
||||
// For all other states, just use the current stashed-in-app setting (e.g. if long clicked).
|
||||
controller.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, forceStashed);
|
||||
controller.updateStateForFlag(FLAG_IN_APP, !forceStashed);
|
||||
return controller.applyStateWithoutStart(duration);
|
||||
}
|
||||
|
||||
private void animateToRecentsState(RecentsState toState) {
|
||||
Animator anim = createAnimToRecentsState(toState, TASKBAR_STASH_DURATION);
|
||||
if (anim != null) {
|
||||
anim.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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.taskbar;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
|
||||
public class ImeBarView extends RelativeLayout {
|
||||
|
||||
private ButtonProvider mButtonProvider;
|
||||
private View mImeView;
|
||||
|
||||
public ImeBarView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public ImeBarView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public ImeBarView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public void init(ButtonProvider buttonProvider) {
|
||||
mButtonProvider = buttonProvider;
|
||||
|
||||
ActivityContext context = getActivityContext();
|
||||
RelativeLayout.LayoutParams imeParams = new RelativeLayout.LayoutParams(
|
||||
context.getDeviceProfile().iconSizePx,
|
||||
context.getDeviceProfile().iconSizePx
|
||||
);
|
||||
RelativeLayout.LayoutParams downParams = new RelativeLayout.LayoutParams(imeParams);
|
||||
|
||||
imeParams.addRule(ALIGN_PARENT_END);
|
||||
imeParams.setMarginEnd(context.getDeviceProfile().iconSizePx);
|
||||
downParams.setMarginStart(context.getDeviceProfile().iconSizePx);
|
||||
downParams.addRule(ALIGN_PARENT_START);
|
||||
|
||||
// Down Arrow
|
||||
View downView = mButtonProvider.getDown();
|
||||
downView.setLayoutParams(downParams);
|
||||
downView.setRotation(-90);
|
||||
addView(downView);
|
||||
|
||||
// IME switcher button
|
||||
mImeView = mButtonProvider.getImeSwitcher();
|
||||
mImeView.setLayoutParams(imeParams);
|
||||
addView(mImeView);
|
||||
}
|
||||
|
||||
public void setImeSwitcherVisibility(boolean show) {
|
||||
mImeView.setVisibility(show ? VISIBLE : GONE);
|
||||
}
|
||||
|
||||
private <T extends Context & ActivityContext> T getActivityContext() {
|
||||
return ActivityContext.lookupContext(getContext());
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,18 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.view.MotionEvent;
|
||||
import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_RESUMED;
|
||||
import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import android.animation.Animator;
|
||||
import android.annotation.ColorInt;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.TaskTransitionSpec;
|
||||
import android.view.WindowManagerGlobal;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.BaseQuickstepLauncher;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
@@ -29,188 +34,116 @@ import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.QuickstepTransitionManager;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.logging.InstanceId;
|
||||
import com.android.launcher3.logging.InstanceIdSequence;
|
||||
import com.android.launcher3.model.data.ItemInfoWithIcon;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.util.OnboardingPrefs;
|
||||
import com.android.quickstep.AnimatedFloat;
|
||||
import com.android.quickstep.RecentsAnimationCallbacks;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A data source which integrates with a Launcher instance
|
||||
* TODO: Rename to have Launcher prefix
|
||||
*/
|
||||
|
||||
public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
|
||||
private static final String TAG = "TaskbarUIController";
|
||||
|
||||
private final BaseQuickstepLauncher mLauncher;
|
||||
private final TaskbarStateHandler mTaskbarStateHandler;
|
||||
private final TaskbarAnimationController mTaskbarAnimationController;
|
||||
private final TaskbarHotseatController mHotseatController;
|
||||
|
||||
private final TaskbarActivityContext mContext;
|
||||
final TaskbarDragLayer mTaskbarDragLayer;
|
||||
final TaskbarView mTaskbarView;
|
||||
private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
|
||||
this::onStashedInAppChanged;
|
||||
|
||||
private @Nullable Animator mAnimator;
|
||||
private boolean mIsAnimatingToLauncher;
|
||||
// Initialized in init.
|
||||
private AnimatedFloat mTaskbarOverrideBackgroundAlpha;
|
||||
private TaskbarKeyguardController mKeyguardController;
|
||||
private final TaskbarLauncherStateController
|
||||
mTaskbarLauncherStateController = new TaskbarLauncherStateController();
|
||||
|
||||
public LauncherTaskbarUIController(
|
||||
BaseQuickstepLauncher launcher, TaskbarActivityContext context) {
|
||||
mContext = context;
|
||||
mTaskbarDragLayer = context.getDragLayer();
|
||||
mTaskbarView = mTaskbarDragLayer.findViewById(R.id.taskbar_view);
|
||||
private final DeviceProfile.OnDeviceProfileChangeListener mProfileChangeListener =
|
||||
new DeviceProfile.OnDeviceProfileChangeListener() {
|
||||
@Override
|
||||
public void onDeviceProfileChanged(DeviceProfile dp) {
|
||||
mControllers.taskbarViewController.onRotationChanged(
|
||||
mLauncher.getDeviceProfile());
|
||||
}
|
||||
};
|
||||
|
||||
public LauncherTaskbarUIController(BaseQuickstepLauncher launcher) {
|
||||
mLauncher = launcher;
|
||||
mTaskbarStateHandler = mLauncher.getTaskbarStateHandler();
|
||||
mTaskbarAnimationController = new TaskbarAnimationController(mLauncher,
|
||||
createTaskbarAnimationControllerCallbacks());
|
||||
mHotseatController = new TaskbarHotseatController(
|
||||
mLauncher, mTaskbarView::updateHotseatItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate() {
|
||||
mTaskbarStateHandler.setAnimationController(mTaskbarAnimationController);
|
||||
mTaskbarAnimationController.init();
|
||||
mHotseatController.init();
|
||||
setTaskbarViewVisible(!mLauncher.hasBeenResumed());
|
||||
alignRealHotseatWithTaskbar();
|
||||
protected void init(TaskbarControllers taskbarControllers) {
|
||||
super.init(taskbarControllers);
|
||||
|
||||
mTaskbarLauncherStateController.init(mControllers, mLauncher);
|
||||
mTaskbarOverrideBackgroundAlpha = mControllers.taskbarDragLayerController
|
||||
.getOverrideBackgroundAlpha();
|
||||
|
||||
mLauncher.setTaskbarUIController(this);
|
||||
mKeyguardController = taskbarControllers.taskbarKeyguardController;
|
||||
|
||||
onLauncherResumedOrPaused(mLauncher.hasBeenResumed(), true /* fromInit */);
|
||||
|
||||
onStashedInAppChanged(mLauncher.getDeviceProfile());
|
||||
mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
|
||||
mLauncher.addOnDeviceProfileChangeListener(mProfileChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (mAnimator != null) {
|
||||
// End this first, in case it relies on properties that are about to be cleaned up.
|
||||
mAnimator.end();
|
||||
}
|
||||
mTaskbarStateHandler.setAnimationController(null);
|
||||
mTaskbarAnimationController.cleanup();
|
||||
mHotseatController.cleanup();
|
||||
setTaskbarViewVisible(true);
|
||||
mLauncher.getHotseat().setIconsAlpha(1f);
|
||||
super.onDestroy();
|
||||
onLauncherResumedOrPaused(false);
|
||||
mTaskbarLauncherStateController.onDestroy();
|
||||
|
||||
mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
|
||||
mLauncher.setTaskbarUIController(null);
|
||||
mLauncher.removeOnDeviceProfileChangeListener(mProfileChangeListener);
|
||||
updateTaskTransitionSpec(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isTaskbarTouchable() {
|
||||
return !mIsAnimatingToLauncher;
|
||||
}
|
||||
|
||||
private TaskbarAnimationControllerCallbacks createTaskbarAnimationControllerCallbacks() {
|
||||
return new TaskbarAnimationControllerCallbacks() {
|
||||
@Override
|
||||
public void updateTaskbarBackgroundAlpha(float alpha) {
|
||||
mTaskbarDragLayer.setTaskbarBackgroundAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTaskbarVisibilityAlpha(float alpha) {
|
||||
mTaskbarView.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateImeBarVisibilityAlpha(float alpha) {
|
||||
mTaskbarDragLayer.updateImeBarVisibilityAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTaskbarScale(float scale) {
|
||||
mTaskbarView.setScaleX(scale);
|
||||
mTaskbarView.setScaleY(scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTaskbarTranslationY(float translationY) {
|
||||
if (translationY < 0) {
|
||||
// Resize to accommodate the max translation we'll reach.
|
||||
mContext.setTaskbarWindowHeight(mContext.getDeviceProfile().taskbarSize
|
||||
+ mLauncher.getHotseat().getTaskbarOffsetY());
|
||||
} else {
|
||||
mContext.setTaskbarWindowHeight(mContext.getDeviceProfile().taskbarSize);
|
||||
}
|
||||
mTaskbarView.setTranslationY(translationY);
|
||||
}
|
||||
};
|
||||
return !mTaskbarLauncherStateController.isAnimatingToLauncher();
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
|
||||
*/
|
||||
public void onLauncherResumedOrPaused(boolean isResumed) {
|
||||
long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION;
|
||||
if (mAnimator != null) {
|
||||
mAnimator.cancel();
|
||||
}
|
||||
if (isResumed) {
|
||||
mAnimator = createAnimToLauncher(null, duration);
|
||||
} else {
|
||||
mAnimator = createAnimToApp(duration);
|
||||
}
|
||||
mAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mAnimator = null;
|
||||
onLauncherResumedOrPaused(isResumed, false /* fromInit */);
|
||||
}
|
||||
|
||||
private void onLauncherResumedOrPaused(boolean isResumed, boolean fromInit) {
|
||||
if (mKeyguardController.isScreenOff()) {
|
||||
if (!isResumed) {
|
||||
return;
|
||||
} else {
|
||||
// Resuming implicitly means device unlocked
|
||||
mKeyguardController.setScreenOn();
|
||||
}
|
||||
});
|
||||
mAnimator.start();
|
||||
}
|
||||
|
||||
mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, isResumed);
|
||||
mTaskbarLauncherStateController.applyState(
|
||||
fromInit ? 0 : QuickstepTransitionManager.CONTENT_ALPHA_DURATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Taskbar animation when going from an app to Launcher.
|
||||
* Create Taskbar animation when going from an app to Launcher as part of recents transition.
|
||||
* @param toState If known, the state we will end up in when reaching Launcher.
|
||||
* @param callbacks callbacks to track the recents animation lifecycle. The state change is
|
||||
* automatically reset once the recents animation finishes
|
||||
*/
|
||||
public Animator createAnimToLauncher(@Nullable LauncherState toState, long duration) {
|
||||
PendingAnimation anim = new PendingAnimation(duration);
|
||||
anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(0, duration));
|
||||
if (toState != null) {
|
||||
mTaskbarStateHandler.setStateWithAnimation(toState, new StateAnimationConfig(), anim);
|
||||
}
|
||||
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mIsAnimatingToLauncher = true;
|
||||
mTaskbarView.setHolesAllowedInLayout(true);
|
||||
mTaskbarView.updateHotseatItemsVisibility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mIsAnimatingToLauncher = false;
|
||||
setTaskbarViewVisible(false);
|
||||
}
|
||||
});
|
||||
|
||||
return anim.buildAnim();
|
||||
}
|
||||
|
||||
private Animator createAnimToApp(long duration) {
|
||||
PendingAnimation anim = new PendingAnimation(duration);
|
||||
anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(1, duration));
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mTaskbarView.updateHotseatItemsVisibility();
|
||||
setTaskbarViewVisible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mTaskbarView.setHolesAllowedInLayout(false);
|
||||
}
|
||||
});
|
||||
return anim.buildAnim();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onImeVisible(TaskbarDragLayer containerView, boolean isVisible) {
|
||||
mTaskbarAnimationController.animateToVisibilityForIme(isVisible ? 0 : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when one or more items in the Hotseat have changed.
|
||||
*/
|
||||
public void onHotseatUpdated() {
|
||||
mHotseatController.onHotseatUpdated();
|
||||
public Animator createAnimToLauncher(@NonNull LauncherState toState,
|
||||
@NonNull RecentsAnimationCallbacks callbacks, long duration) {
|
||||
return mTaskbarLauncherStateController.createAnimToLauncher(toState, callbacks, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,58 +151,99 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
* @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
|
||||
*/
|
||||
public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
|
||||
return mTaskbarView.isEventOverAnyItem(ev);
|
||||
return mControllers.taskbarViewController.isEventOverAnyItem(ev)
|
||||
|| mControllers.navbarButtonsViewController.isEventOverAnyItem(ev);
|
||||
}
|
||||
|
||||
public boolean isDraggingItem() {
|
||||
return mTaskbarView.isDraggingItem();
|
||||
return mControllers.taskbarDragController.isDragging();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat.
|
||||
*/
|
||||
@Override
|
||||
public void alignRealHotseatWithTaskbar() {
|
||||
Rect hotseatBounds = new Rect();
|
||||
DeviceProfile grid = mLauncher.getDeviceProfile();
|
||||
int hotseatHeight = grid.workspacePadding.bottom + grid.taskbarSize;
|
||||
int taskbarOffset = mLauncher.getHotseat().getTaskbarOffsetY();
|
||||
int hotseatTopDiff = hotseatHeight - grid.taskbarSize - taskbarOffset;
|
||||
int hotseatBottomDiff = taskbarOffset;
|
||||
protected void onStashedInAppChanged() {
|
||||
onStashedInAppChanged(mLauncher.getDeviceProfile());
|
||||
}
|
||||
|
||||
RectF hotseatBoundsF = mTaskbarView.getHotseatBounds();
|
||||
Utilities.scaleRectFAboutPivot(hotseatBoundsF, getTaskbarScaleOnHome(),
|
||||
mTaskbarView.getPivotX(), mTaskbarView.getPivotY());
|
||||
hotseatBoundsF.round(hotseatBounds);
|
||||
mLauncher.getHotseat().setPadding(hotseatBounds.left,
|
||||
hotseatBounds.top + hotseatTopDiff,
|
||||
mTaskbarView.getWidth() - hotseatBounds.right,
|
||||
mTaskbarView.getHeight() - hotseatBounds.bottom + hotseatBottomDiff);
|
||||
private void onStashedInAppChanged(DeviceProfile deviceProfile) {
|
||||
boolean taskbarStashedInApps = mControllers.taskbarStashController.isStashedInApp();
|
||||
deviceProfile.isTaskbarPresentInApps = !taskbarStashedInApps;
|
||||
updateTaskTransitionSpec(taskbarStashedInApps);
|
||||
}
|
||||
|
||||
private void updateTaskTransitionSpec(boolean taskbarIsHidden) {
|
||||
try {
|
||||
if (taskbarIsHidden) {
|
||||
// Clear custom task transition settings when the taskbar is stashed
|
||||
WindowManagerGlobal.getWindowManagerService().clearTaskTransitionSpec();
|
||||
} else {
|
||||
// Adjust task transition spec to account for taskbar being visible
|
||||
@ColorInt int taskAnimationBackgroundColor =
|
||||
mLauncher.getColor(R.color.taskbar_background);
|
||||
|
||||
TaskTransitionSpec customTaskAnimationSpec = new TaskTransitionSpec(
|
||||
taskAnimationBackgroundColor,
|
||||
Set.of(ITYPE_EXTRA_NAVIGATION_BAR)
|
||||
);
|
||||
WindowManagerGlobal.getWindowManagerService()
|
||||
.setTaskTransitionSpec(customTaskAnimationSpec);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
// This shouldn't happen but if it does task animations won't look good until the
|
||||
// taskbar stashing state is changed.
|
||||
Log.e(TAG, "Failed to update task transition spec to account for new taskbar state",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ratio of the taskbar icon size on home vs in an app.
|
||||
* Sets whether the background behind the taskbar/nav bar should be hidden.
|
||||
*/
|
||||
public float getTaskbarScaleOnHome() {
|
||||
DeviceProfile inAppDp = mContext.getDeviceProfile();
|
||||
DeviceProfile onHomeDp = mLauncher.getDeviceProfile();
|
||||
return (float) onHomeDp.cellWidthPx / inAppDp.cellWidthPx;
|
||||
public void forceHideBackground(boolean forceHide) {
|
||||
mTaskbarOverrideBackgroundAlpha.updateValue(forceHide ? 0 : 1);
|
||||
}
|
||||
|
||||
void setTaskbarViewVisible(boolean isVisible) {
|
||||
mTaskbarView.setIconsVisibility(isVisible);
|
||||
mLauncher.getHotseat().setIconsAlpha(isVisible ? 0f : 1f);
|
||||
@Override
|
||||
public Stream<ItemInfoWithIcon> getAppIconsForEdu() {
|
||||
return Arrays.stream(mLauncher.getAppsView().getAppsStore().getApps());
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains methods that TaskbarAnimationController can call to interface with
|
||||
* TaskbarController.
|
||||
* Starts the taskbar education flow, if the user hasn't seen it yet.
|
||||
*/
|
||||
protected interface TaskbarAnimationControllerCallbacks {
|
||||
void updateTaskbarBackgroundAlpha(float alpha);
|
||||
void updateTaskbarVisibilityAlpha(float alpha);
|
||||
void updateImeBarVisibilityAlpha(float alpha);
|
||||
void updateTaskbarScale(float scale);
|
||||
void updateTaskbarTranslationY(float translationY);
|
||||
public void showEdu() {
|
||||
if (!FeatureFlags.ENABLE_TASKBAR_EDU.get()
|
||||
|| Utilities.IS_RUNNING_IN_TEST_HARNESS
|
||||
|| mLauncher.getOnboardingPrefs().getBoolean(OnboardingPrefs.TASKBAR_EDU_SEEN)) {
|
||||
return;
|
||||
}
|
||||
mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.TASKBAR_EDU_SEEN);
|
||||
|
||||
mControllers.taskbarEduController.showEdu();
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually ends the taskbar education flow.
|
||||
*/
|
||||
public void hideEdu() {
|
||||
if (!FeatureFlags.ENABLE_TASKBAR_EDU.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mControllers.taskbarEduController.hideEdu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskbarIconLaunched(WorkspaceItemInfo item) {
|
||||
InstanceId instanceId = new InstanceIdSequence().newInstanceId();
|
||||
mLauncher.logAppLaunch(mControllers.taskbarActivityContext.getStatsLogManager(), item,
|
||||
instanceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSystemGestureInProgress(boolean inProgress) {
|
||||
super.setSystemGestureInProgress(inProgress);
|
||||
// Launcher's ScrimView will draw the background throughout the gesture. But once the
|
||||
// gesture ends, start drawing taskbar's background again since launcher might stop drawing.
|
||||
forceHideBackground(inProgress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,612 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
|
||||
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
|
||||
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
|
||||
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
|
||||
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
|
||||
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
|
||||
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD;
|
||||
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_BACK_DISABLED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
|
||||
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.DrawableRes;
|
||||
import android.annotation.IdRes;
|
||||
import android.annotation.LayoutRes;
|
||||
import android.content.pm.ActivityInfo.Config;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.Region.Op;
|
||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||
import android.util.Property;
|
||||
import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnHoverListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.android.launcher3.LauncherAnimUtils;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AlphaUpdateListener;
|
||||
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
|
||||
import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.quickstep.AnimatedFloat;
|
||||
import com.android.systemui.shared.rotation.FloatingRotationButton;
|
||||
import com.android.systemui.shared.rotation.RotationButton;
|
||||
import com.android.systemui.shared.rotation.RotationButtonController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.IntPredicate;
|
||||
|
||||
/**
|
||||
* Controller for managing nav bar buttons in taskbar
|
||||
*/
|
||||
public class NavbarButtonsViewController {
|
||||
|
||||
private final Rect mTempRect = new Rect();
|
||||
|
||||
private static final int FLAG_SWITCHER_SUPPORTED = 1 << 0;
|
||||
private static final int FLAG_IME_VISIBLE = 1 << 1;
|
||||
private static final int FLAG_ROTATION_BUTTON_VISIBLE = 1 << 2;
|
||||
private static final int FLAG_A11Y_VISIBLE = 1 << 3;
|
||||
private static final int FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE = 1 << 4;
|
||||
private static final int FLAG_KEYGUARD_VISIBLE = 1 << 5;
|
||||
private static final int FLAG_KEYGUARD_OCCLUDED = 1 << 6;
|
||||
private static final int FLAG_DISABLE_HOME = 1 << 7;
|
||||
private static final int FLAG_DISABLE_RECENTS = 1 << 8;
|
||||
private static final int FLAG_DISABLE_BACK = 1 << 9;
|
||||
private static final int FLAG_NOTIFICATION_SHADE_EXPANDED = 1 << 10;
|
||||
private static final int FLAG_SCREEN_PINNING_ACTIVE = 1 << 11;
|
||||
|
||||
private static final int MASK_IME_SWITCHER_VISIBLE = FLAG_SWITCHER_SUPPORTED | FLAG_IME_VISIBLE;
|
||||
|
||||
private final ArrayList<StatePropertyHolder> mPropertyHolders = new ArrayList<>();
|
||||
private final ArrayList<ImageView> mAllButtons = new ArrayList<>();
|
||||
private int mState;
|
||||
|
||||
private final TaskbarActivityContext mContext;
|
||||
private final FrameLayout mNavButtonsView;
|
||||
private final ViewGroup mNavButtonContainer;
|
||||
// Used for IME+A11Y buttons
|
||||
private final ViewGroup mEndContextualContainer;
|
||||
private final ViewGroup mStartContextualContainer;
|
||||
private final int mLightIconColor;
|
||||
private final int mDarkIconColor;
|
||||
|
||||
private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat(
|
||||
this::updateNavButtonTranslationY);
|
||||
private final AnimatedFloat mTaskbarNavButtonTranslationYForIme = new AnimatedFloat(
|
||||
this::updateNavButtonTranslationY);
|
||||
// Only applies to mTaskbarNavButtonTranslationY
|
||||
private final AnimatedFloat mNavButtonTranslationYMultiplier = new AnimatedFloat(
|
||||
this::updateNavButtonTranslationY);
|
||||
private final AnimatedFloat mTaskbarNavButtonDarkIntensity = new AnimatedFloat(
|
||||
this::updateNavButtonDarkIntensity);
|
||||
private final AnimatedFloat mNavButtonDarkIntensityMultiplier = new AnimatedFloat(
|
||||
this::updateNavButtonDarkIntensity);
|
||||
private final RotationButtonListener mRotationButtonListener = new RotationButtonListener();
|
||||
|
||||
private final Rect mFloatingRotationButtonBounds = new Rect();
|
||||
|
||||
// Initialized in init.
|
||||
private TaskbarControllers mControllers;
|
||||
private View mA11yButton;
|
||||
private int mSysuiStateFlags;
|
||||
private View mBackButton;
|
||||
private FloatingRotationButton mFloatingRotationButton;
|
||||
|
||||
public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
|
||||
mContext = context;
|
||||
mNavButtonsView = navButtonsView;
|
||||
mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
|
||||
mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons);
|
||||
mStartContextualContainer = mNavButtonsView.findViewById(R.id.start_contextual_buttons);
|
||||
|
||||
mLightIconColor = context.getColor(R.color.taskbar_nav_icon_light_color);
|
||||
mDarkIconColor = context.getColor(R.color.taskbar_nav_icon_dark_color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the controller
|
||||
*/
|
||||
public void init(TaskbarControllers controllers) {
|
||||
mControllers = controllers;
|
||||
mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarSize;
|
||||
mNavButtonTranslationYMultiplier.value = 1;
|
||||
|
||||
boolean isThreeButtonNav = mContext.isThreeButtonNav();
|
||||
// IME switcher
|
||||
View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
|
||||
isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
|
||||
mControllers.navButtonController, R.id.ime_switcher);
|
||||
mPropertyHolders.add(new StatePropertyHolder(imeSwitcherButton,
|
||||
flags -> ((flags & MASK_IME_SWITCHER_VISIBLE) == MASK_IME_SWITCHER_VISIBLE)
|
||||
&& ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)));
|
||||
|
||||
mPropertyHolders.add(new StatePropertyHolder(
|
||||
mControllers.taskbarViewController.getTaskbarIconAlpha()
|
||||
.getProperty(ALPHA_INDEX_KEYGUARD),
|
||||
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0
|
||||
&& (flags & FLAG_SCREEN_PINNING_ACTIVE) == 0,
|
||||
MultiValueAlpha.VALUE, 1, 0));
|
||||
|
||||
mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController
|
||||
.getKeyguardBgTaskbar(),
|
||||
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0, AnimatedFloat.VALUE, 1, 0));
|
||||
|
||||
// Force nav buttons (specifically back button) to be visible during setup wizard.
|
||||
boolean isInSetup = !mContext.isUserSetupComplete();
|
||||
boolean alwaysShowButtons = isThreeButtonNav || isInSetup;
|
||||
|
||||
// Make sure to remove nav bar buttons translation when notification shade is expanded or
|
||||
// IME is showing (add separate translation for IME).
|
||||
int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE;
|
||||
mPropertyHolders.add(new StatePropertyHolder(mNavButtonTranslationYMultiplier,
|
||||
flags -> (flags & flagsToRemoveTranslation) != 0, AnimatedFloat.VALUE,
|
||||
0, 1));
|
||||
// Center nav buttons in new height for IME.
|
||||
float transForIme = (mContext.getDeviceProfile().taskbarSize
|
||||
- mContext.getTaskbarHeightForIme()) / 2f;
|
||||
// For gesture nav, nav buttons only show for IME anyway so keep them translated down.
|
||||
float defaultButtonTransY = alwaysShowButtons ? 0 : transForIme;
|
||||
mPropertyHolders.add(new StatePropertyHolder(mTaskbarNavButtonTranslationYForIme,
|
||||
flags -> (flags & FLAG_IME_VISIBLE) != 0, AnimatedFloat.VALUE, transForIme,
|
||||
defaultButtonTransY));
|
||||
|
||||
if (alwaysShowButtons) {
|
||||
initButtons(mNavButtonContainer, mEndContextualContainer,
|
||||
mControllers.navButtonController);
|
||||
|
||||
if (isInSetup) {
|
||||
// Since setup wizard only has back button enabled, it looks strange to be
|
||||
// end-aligned, so start-align instead.
|
||||
FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
|
||||
mNavButtonContainer.getLayoutParams();
|
||||
navButtonsLayoutParams.setMarginStart(navButtonsLayoutParams.getMarginEnd());
|
||||
navButtonsLayoutParams.setMarginEnd(0);
|
||||
navButtonsLayoutParams.gravity = Gravity.START;
|
||||
mNavButtonContainer.requestLayout();
|
||||
|
||||
// TODO(b/210906568) Dark intensity is currently not propagated during setup, so set
|
||||
// it based on dark theme for now.
|
||||
int mode = mContext.getResources().getConfiguration().uiMode
|
||||
& Configuration.UI_MODE_NIGHT_MASK;
|
||||
boolean isDarkTheme = mode == Configuration.UI_MODE_NIGHT_YES;
|
||||
mTaskbarNavButtonDarkIntensity.updateValue(isDarkTheme ? 0 : 1);
|
||||
}
|
||||
|
||||
// Animate taskbar background when any of these flags are enabled
|
||||
int flagsToShowBg = FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE
|
||||
| FLAG_NOTIFICATION_SHADE_EXPANDED;
|
||||
mPropertyHolders.add(new StatePropertyHolder(
|
||||
mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
|
||||
flags -> (flags & flagsToShowBg) != 0, AnimatedFloat.VALUE, 1, 0));
|
||||
|
||||
// Rotation button
|
||||
RotationButton rotationButton = new RotationButtonImpl(
|
||||
addButton(mEndContextualContainer, R.id.rotate_suggestion,
|
||||
R.layout.taskbar_contextual_button));
|
||||
rotationButton.hide();
|
||||
mControllers.rotationButtonController.setRotationButton(rotationButton, null);
|
||||
} else {
|
||||
mFloatingRotationButton = new FloatingRotationButton(mContext,
|
||||
R.string.accessibility_rotate_button,
|
||||
R.layout.rotate_suggestion,
|
||||
R.id.rotate_suggestion,
|
||||
R.dimen.floating_rotation_button_min_margin,
|
||||
R.dimen.rounded_corner_content_padding,
|
||||
R.dimen.floating_rotation_button_taskbar_left_margin,
|
||||
R.dimen.floating_rotation_button_taskbar_bottom_margin,
|
||||
R.dimen.floating_rotation_button_diameter,
|
||||
R.dimen.key_button_ripple_max_width);
|
||||
mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton,
|
||||
mRotationButtonListener);
|
||||
|
||||
View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
|
||||
mStartContextualContainer, mControllers.navButtonController, R.id.back);
|
||||
imeDownButton.setRotation(Utilities.isRtl(mContext.getResources()) ? 90 : -90);
|
||||
// Rotate when Ime visible
|
||||
mPropertyHolders.add(new StatePropertyHolder(imeDownButton,
|
||||
flags -> (flags & FLAG_IME_VISIBLE) != 0));
|
||||
}
|
||||
|
||||
applyState();
|
||||
mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
|
||||
}
|
||||
|
||||
private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
|
||||
TaskbarNavButtonController navButtonController) {
|
||||
|
||||
mBackButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
|
||||
mNavButtonContainer, mControllers.navButtonController, R.id.back);
|
||||
mPropertyHolders.add(new StatePropertyHolder(mBackButton,
|
||||
flags -> {
|
||||
// Show only if not disabled, and if not on the keyguard or otherwise only when
|
||||
// the bouncer or a lockscreen app is showing above the keyguard
|
||||
boolean showingOnKeyguard = (flags & FLAG_KEYGUARD_VISIBLE) == 0 ||
|
||||
(flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0 ||
|
||||
(flags & FLAG_KEYGUARD_OCCLUDED) != 0;
|
||||
return (flags & FLAG_DISABLE_BACK) == 0
|
||||
&& ((flags & FLAG_KEYGUARD_VISIBLE) == 0 || showingOnKeyguard);
|
||||
}));
|
||||
boolean isRtl = Utilities.isRtl(mContext.getResources());
|
||||
mPropertyHolders.add(new StatePropertyHolder(
|
||||
mBackButton, flags -> (flags & FLAG_IME_VISIBLE) != 0, View.ROTATION,
|
||||
isRtl ? 90 : -90, 0));
|
||||
// Translate back button to be at end/start of other buttons for keyguard
|
||||
int navButtonSize = mContext.getResources().getDimensionPixelSize(
|
||||
R.dimen.taskbar_nav_buttons_size);
|
||||
mPropertyHolders.add(new StatePropertyHolder(
|
||||
mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
|
||||
|| (flags & FLAG_KEYGUARD_VISIBLE) != 0,
|
||||
VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));
|
||||
|
||||
|
||||
// home and recents buttons
|
||||
View homeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
|
||||
navButtonController, R.id.home);
|
||||
mPropertyHolders.add(new StatePropertyHolder(homeButton,
|
||||
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
|
||||
(flags & FLAG_DISABLE_HOME) == 0));
|
||||
View recentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
|
||||
navContainer, navButtonController, R.id.recent_apps);
|
||||
mPropertyHolders.add(new StatePropertyHolder(recentsButton,
|
||||
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
|
||||
(flags & FLAG_DISABLE_RECENTS) == 0));
|
||||
|
||||
// A11y button
|
||||
mA11yButton = addButton(R.drawable.ic_sysbar_accessibility_button, BUTTON_A11Y,
|
||||
endContainer, navButtonController, R.id.accessibility_button,
|
||||
R.layout.taskbar_contextual_button);
|
||||
mPropertyHolders.add(new StatePropertyHolder(mA11yButton,
|
||||
flags -> (flags & FLAG_A11Y_VISIBLE) != 0
|
||||
&& (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0));
|
||||
}
|
||||
|
||||
private void parseSystemUiFlags(int sysUiStateFlags) {
|
||||
mSysuiStateFlags = sysUiStateFlags;
|
||||
boolean isImeVisible = (sysUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
|
||||
boolean isImeSwitcherShowing = (sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0;
|
||||
boolean a11yVisible = (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
|
||||
boolean isHomeDisabled = (sysUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
|
||||
boolean isRecentsDisabled = (sysUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
|
||||
boolean isBackDisabled = (sysUiStateFlags & SYSUI_STATE_BACK_DISABLED) != 0;
|
||||
int shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
|
||||
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
|
||||
boolean isNotificationShadeExpanded = (sysUiStateFlags & shadeExpandedFlags) != 0;
|
||||
boolean isScreenPinningActive = (sysUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
|
||||
|
||||
// TODO(b/202218289) we're getting IME as not visible on lockscreen from system
|
||||
updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
|
||||
updateStateForFlag(FLAG_SWITCHER_SUPPORTED, isImeSwitcherShowing);
|
||||
updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible);
|
||||
updateStateForFlag(FLAG_DISABLE_HOME, isHomeDisabled);
|
||||
updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled);
|
||||
updateStateForFlag(FLAG_DISABLE_BACK, isBackDisabled);
|
||||
updateStateForFlag(FLAG_NOTIFICATION_SHADE_EXPANDED, isNotificationShadeExpanded);
|
||||
updateStateForFlag(FLAG_SCREEN_PINNING_ACTIVE, isScreenPinningActive);
|
||||
|
||||
if (mA11yButton != null) {
|
||||
// Only used in 3 button
|
||||
boolean a11yLongClickable =
|
||||
(sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
|
||||
mA11yButton.setLongClickable(a11yLongClickable);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) {
|
||||
if (systemUiStateFlags == mSysuiStateFlags) {
|
||||
return;
|
||||
}
|
||||
parseSystemUiFlags(systemUiStateFlags);
|
||||
applyState();
|
||||
if (skipAnim) {
|
||||
mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when we need to show back button for bouncer
|
||||
*/
|
||||
public void setBackForBouncer(boolean isBouncerVisible) {
|
||||
updateStateForFlag(FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE, isBouncerVisible);
|
||||
applyState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Slightly misnamed, but should be called when keyguard OR AOD is showing.
|
||||
* We consider keyguardVisible when it's showing bouncer OR is occlucded by another app
|
||||
*/
|
||||
public void setKeyguardVisible(boolean isKeyguardVisible, boolean isKeyguardOccluded) {
|
||||
updateStateForFlag(FLAG_KEYGUARD_VISIBLE, isKeyguardVisible || isKeyguardOccluded);
|
||||
updateStateForFlag(FLAG_KEYGUARD_OCCLUDED, isKeyguardOccluded);
|
||||
applyState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if IME bar is visible
|
||||
*/
|
||||
public boolean isImeVisible() {
|
||||
return (mState & FLAG_IME_VISIBLE) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the home button is disabled
|
||||
*/
|
||||
public boolean isHomeDisabled() {
|
||||
return (mState & FLAG_DISABLE_HOME) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the recents (overview) button is disabled
|
||||
*/
|
||||
public boolean isRecentsDisabled() {
|
||||
return (mState & FLAG_DISABLE_RECENTS) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the bounds corresponding to all visible buttons to provided region
|
||||
*/
|
||||
public void addVisibleButtonsRegion(TaskbarDragLayer parent, Region outRegion) {
|
||||
int count = mAllButtons.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
View button = mAllButtons.get(i);
|
||||
if (button.getVisibility() == View.VISIBLE) {
|
||||
parent.getDescendantRectRelativeToSelf(button, mTempRect);
|
||||
outRegion.op(mTempRect, Op.UNION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Use to set the translationY for the all nav+contextual buttons */
|
||||
public AnimatedFloat getTaskbarNavButtonTranslationY() {
|
||||
return mTaskbarNavButtonTranslationY;
|
||||
}
|
||||
|
||||
/** Use to set the dark intensity for the all nav+contextual buttons */
|
||||
public AnimatedFloat getTaskbarNavButtonDarkIntensity() {
|
||||
return mTaskbarNavButtonDarkIntensity;
|
||||
}
|
||||
|
||||
/** Use to determine whether to use the dark intensity requested by the underlying app */
|
||||
public AnimatedFloat getNavButtonDarkIntensityMultiplier() {
|
||||
return mNavButtonDarkIntensityMultiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not call {@link #applyState()}. Don't forget to!
|
||||
*/
|
||||
private void updateStateForFlag(int flag, boolean enabled) {
|
||||
if (enabled) {
|
||||
mState |= flag;
|
||||
} else {
|
||||
mState &= ~flag;
|
||||
}
|
||||
}
|
||||
|
||||
private void applyState() {
|
||||
int count = mPropertyHolders.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
mPropertyHolders.get(i).setState(mState);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNavButtonTranslationY() {
|
||||
float normalTranslationY = mTaskbarNavButtonTranslationY.value
|
||||
* mNavButtonTranslationYMultiplier.value;
|
||||
float otherTranslationY = mTaskbarNavButtonTranslationYForIme.value;
|
||||
mNavButtonsView.setTranslationY(normalTranslationY + otherTranslationY);
|
||||
}
|
||||
|
||||
private void updateNavButtonDarkIntensity() {
|
||||
float darkIntensity = mTaskbarNavButtonDarkIntensity.value
|
||||
* mNavButtonDarkIntensityMultiplier.value;
|
||||
int iconColor = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, mLightIconColor,
|
||||
mDarkIconColor);
|
||||
for (ImageView button : mAllButtons) {
|
||||
button.setImageTintList(ColorStateList.valueOf(iconColor));
|
||||
}
|
||||
}
|
||||
|
||||
private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
|
||||
ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id) {
|
||||
return addButton(drawableId, buttonType, parent, navButtonController, id,
|
||||
R.layout.taskbar_nav_button);
|
||||
}
|
||||
|
||||
private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
|
||||
ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id,
|
||||
@LayoutRes int layoutId) {
|
||||
ImageView buttonView = addButton(parent, id, layoutId);
|
||||
buttonView.setImageResource(drawableId);
|
||||
buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType));
|
||||
buttonView.setOnLongClickListener(view ->
|
||||
navButtonController.onButtonLongClick(buttonType));
|
||||
return buttonView;
|
||||
}
|
||||
|
||||
private ImageView addButton(ViewGroup parent, @IdRes int id, @LayoutRes int layoutId) {
|
||||
ImageView buttonView = (ImageView) mContext.getLayoutInflater()
|
||||
.inflate(layoutId, parent, false);
|
||||
buttonView.setId(id);
|
||||
parent.addView(buttonView);
|
||||
mAllButtons.add(buttonView);
|
||||
return buttonView;
|
||||
}
|
||||
|
||||
public boolean isEventOverAnyItem(MotionEvent ev) {
|
||||
return mFloatingRotationButtonBounds.contains((int) ev.getX(), (int) ev.getY());
|
||||
}
|
||||
|
||||
public void onConfigurationChanged(@Config int configChanges) {
|
||||
if (mFloatingRotationButton != null) {
|
||||
mFloatingRotationButton.onConfigurationChanged(configChanges);
|
||||
}
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
mPropertyHolders.clear();
|
||||
mControllers.rotationButtonController.unregisterListeners();
|
||||
if (mFloatingRotationButton != null) {
|
||||
mFloatingRotationButton.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private class RotationButtonListener implements RotationButton.RotationButtonUpdatesCallback {
|
||||
@Override
|
||||
public void onVisibilityChanged(boolean isVisible) {
|
||||
if (isVisible) {
|
||||
mFloatingRotationButton.getCurrentView()
|
||||
.getBoundsOnScreen(mFloatingRotationButtonBounds);
|
||||
} else {
|
||||
mFloatingRotationButtonBounds.setEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RotationButtonImpl implements RotationButton {
|
||||
|
||||
private final ImageView mButton;
|
||||
private AnimatedVectorDrawable mImageDrawable;
|
||||
|
||||
RotationButtonImpl(ImageView button) {
|
||||
mButton = button;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRotationButtonController(RotationButtonController rotationButtonController) {
|
||||
// TODO(b/187754252) UI polish, different icons based on light/dark context, etc
|
||||
mImageDrawable = (AnimatedVectorDrawable) mButton.getContext()
|
||||
.getDrawable(rotationButtonController.getIconResId());
|
||||
mButton.setImageDrawable(mImageDrawable);
|
||||
mButton.setContentDescription(mButton.getResources()
|
||||
.getString(R.string.accessibility_rotate_button));
|
||||
mImageDrawable.setCallback(mButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getCurrentView() {
|
||||
return mButton;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean show() {
|
||||
mButton.setVisibility(View.VISIBLE);
|
||||
mState |= FLAG_ROTATION_BUTTON_VISIBLE;
|
||||
applyState();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hide() {
|
||||
mButton.setVisibility(View.GONE);
|
||||
mState &= ~FLAG_ROTATION_BUTTON_VISIBLE;
|
||||
applyState();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
return mButton.getVisibility() == View.VISIBLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateIcon(int lightIconColor, int darkIconColor) {
|
||||
// TODO(b/187754252): UI Polish
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClickListener(OnClickListener onClickListener) {
|
||||
mButton.setOnClickListener(onClickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnHoverListener(OnHoverListener onHoverListener) {
|
||||
mButton.setOnHoverListener(onHoverListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimatedVectorDrawable getImageDrawable() {
|
||||
return mImageDrawable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDarkIntensity(float darkIntensity) {
|
||||
// TODO(b/187754252) UI polish
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptRotationProposal() {
|
||||
return mButton.isAttachedToWindow();
|
||||
}
|
||||
}
|
||||
|
||||
private static class StatePropertyHolder {
|
||||
|
||||
private final float mEnabledValue, mDisabledValue;
|
||||
private final ObjectAnimator mAnimator;
|
||||
private final IntPredicate mEnableCondition;
|
||||
|
||||
private boolean mIsEnabled = true;
|
||||
|
||||
StatePropertyHolder(View view, IntPredicate enableCondition) {
|
||||
this(view, enableCondition, LauncherAnimUtils.VIEW_ALPHA, 1, 0);
|
||||
mAnimator.addListener(new AlphaUpdateListener(view));
|
||||
}
|
||||
|
||||
<T> StatePropertyHolder(T target, IntPredicate enabledCondition,
|
||||
Property<T, Float> property, float enabledValue, float disabledValue) {
|
||||
mEnableCondition = enabledCondition;
|
||||
mEnabledValue = enabledValue;
|
||||
mDisabledValue = disabledValue;
|
||||
mAnimator = ObjectAnimator.ofFloat(target, property, enabledValue, disabledValue);
|
||||
}
|
||||
|
||||
public void setState(int flags) {
|
||||
boolean isEnabled = mEnableCondition.test(flags);
|
||||
if (mIsEnabled != isEnabled) {
|
||||
mIsEnabled = isEnabled;
|
||||
mAnimator.cancel();
|
||||
mAnimator.setFloatValues(mIsEnabled ? mEnabledValue : mDisabledValue);
|
||||
mAnimator.start();
|
||||
}
|
||||
}
|
||||
|
||||
public void endAnimation() {
|
||||
if (mAnimator.isRunning()) {
|
||||
mAnimator.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.android.launcher3.LauncherAnimUtils;
|
||||
import com.android.launcher3.R;
|
||||
|
||||
public class StashedHandleView extends View {
|
||||
|
||||
private static final long COLOR_CHANGE_DURATION = 120;
|
||||
|
||||
private final @ColorInt int mStashedHandleLightColor;
|
||||
private final @ColorInt int mStashedHandleDarkColor;
|
||||
private final Rect mSampledRegion = new Rect();
|
||||
private final int[] mTmpArr = new int[2];
|
||||
|
||||
private @Nullable ObjectAnimator mColorChangeAnim;
|
||||
|
||||
public StashedHandleView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public StashedHandleView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public StashedHandleView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public StashedHandleView(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
|
||||
mStashedHandleLightColor = ContextCompat.getColor(context,
|
||||
R.color.taskbar_stashed_handle_light_color);
|
||||
mStashedHandleDarkColor = ContextCompat.getColor(context,
|
||||
R.color.taskbar_stashed_handle_dark_color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates mSampledRegion to be the location of the stashedHandleBounds relative to the screen.
|
||||
* @see #getSampledRegion()
|
||||
*/
|
||||
public void updateSampledRegion(Rect stashedHandleBounds) {
|
||||
getLocationOnScreen(mTmpArr);
|
||||
mSampledRegion.set(stashedHandleBounds);
|
||||
mSampledRegion.offset(mTmpArr[0], mTmpArr[1]);
|
||||
}
|
||||
|
||||
public Rect getSampledRegion() {
|
||||
return mSampledRegion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the handle color.
|
||||
* @param isRegionDark Whether the background behind the handle is dark, and thus the handle
|
||||
* should be light (and vice versa).
|
||||
* @param animate Whether to animate the change, or apply it immediately.
|
||||
*/
|
||||
public void updateHandleColor(boolean isRegionDark, boolean animate) {
|
||||
int newColor = isRegionDark ? mStashedHandleLightColor : mStashedHandleDarkColor;
|
||||
if (mColorChangeAnim != null) {
|
||||
mColorChangeAnim.cancel();
|
||||
}
|
||||
if (animate) {
|
||||
mColorChangeAnim = ObjectAnimator.ofArgb(this,
|
||||
LauncherAnimUtils.VIEW_BACKGROUND_COLOR, newColor);
|
||||
mColorChangeAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mColorChangeAnim = null;
|
||||
}
|
||||
});
|
||||
mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION);
|
||||
mColorChangeAnim.start();
|
||||
} else {
|
||||
setBackgroundColor(newColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.RevealOutlineAnimation;
|
||||
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
|
||||
import com.android.launcher3.util.Executors;
|
||||
import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.quickstep.AnimatedFloat;
|
||||
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
|
||||
|
||||
/**
|
||||
* Handles properties/data collection, then passes the results to our stashed handle View to render.
|
||||
*/
|
||||
public class StashedHandleViewController {
|
||||
|
||||
public static final int ALPHA_INDEX_STASHED = 0;
|
||||
public static final int ALPHA_INDEX_HOME_DISABLED = 1;
|
||||
private static final int NUM_ALPHA_CHANNELS = 2;
|
||||
|
||||
/**
|
||||
* The SharedPreferences key for whether the stashed handle region is dark.
|
||||
*/
|
||||
private static final String SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY =
|
||||
"stashed_handle_region_is_dark";
|
||||
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final SharedPreferences mPrefs;
|
||||
private final StashedHandleView mStashedHandleView;
|
||||
private final int mStashedHandleWidth;
|
||||
private final int mStashedHandleHeight;
|
||||
private final RegionSamplingHelper mRegionSamplingHelper;
|
||||
private final MultiValueAlpha mTaskbarStashedHandleAlpha;
|
||||
private final AnimatedFloat mTaskbarStashedHandleHintScale = new AnimatedFloat(
|
||||
this::updateStashedHandleHintScale);
|
||||
|
||||
// Initialized in init.
|
||||
private TaskbarControllers mControllers;
|
||||
|
||||
// The bounds we want to clip to in the settled state when showing the stashed handle.
|
||||
private final Rect mStashedHandleBounds = new Rect();
|
||||
private float mStashedHandleRadius;
|
||||
|
||||
// When the reveal animation is cancelled, we can assume it's about to create a new animation,
|
||||
// which should start off at the same point the cancelled one left off.
|
||||
private float mStartProgressForNextRevealAnim;
|
||||
private boolean mWasLastRevealAnimReversed;
|
||||
|
||||
public StashedHandleViewController(TaskbarActivityContext activity,
|
||||
StashedHandleView stashedHandleView) {
|
||||
mActivity = activity;
|
||||
mPrefs = Utilities.getPrefs(mActivity);
|
||||
mStashedHandleView = stashedHandleView;
|
||||
mTaskbarStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, NUM_ALPHA_CHANNELS);
|
||||
mTaskbarStashedHandleAlpha.setUpdateVisibility(true);
|
||||
mStashedHandleView.updateHandleColor(
|
||||
mPrefs.getBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY, false),
|
||||
false /* animate */);
|
||||
final Resources resources = mActivity.getResources();
|
||||
mStashedHandleWidth = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width);
|
||||
mStashedHandleHeight = resources.getDimensionPixelSize(
|
||||
R.dimen.taskbar_stashed_handle_height);
|
||||
mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView,
|
||||
new RegionSamplingHelper.SamplingCallback() {
|
||||
@Override
|
||||
public void onRegionDarknessChanged(boolean isRegionDark) {
|
||||
mStashedHandleView.updateHandleColor(isRegionDark, true /* animate */);
|
||||
mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY,
|
||||
isRegionDark).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rect getSampledRegion(View sampledView) {
|
||||
return mStashedHandleView.getSampledRegion();
|
||||
}
|
||||
}, Executors.UI_HELPER_EXECUTOR);
|
||||
}
|
||||
|
||||
public void init(TaskbarControllers controllers) {
|
||||
mControllers = controllers;
|
||||
mStashedHandleView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
|
||||
|
||||
mTaskbarStashedHandleAlpha.getProperty(ALPHA_INDEX_STASHED).setValue(0);
|
||||
mTaskbarStashedHandleHintScale.updateValue(1f);
|
||||
|
||||
final int stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight();
|
||||
mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
final int stashedCenterX = view.getWidth() / 2;
|
||||
final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2;
|
||||
mStashedHandleBounds.set(
|
||||
stashedCenterX - mStashedHandleWidth / 2,
|
||||
stashedCenterY - mStashedHandleHeight / 2,
|
||||
stashedCenterX + mStashedHandleWidth / 2,
|
||||
stashedCenterY + mStashedHandleHeight / 2);
|
||||
mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
|
||||
mStashedHandleRadius = view.getHeight() / 2f;
|
||||
outline.setRoundRect(mStashedHandleBounds, mStashedHandleRadius);
|
||||
}
|
||||
});
|
||||
|
||||
mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> {
|
||||
final int stashedCenterX = view.getWidth() / 2;
|
||||
final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2;
|
||||
|
||||
view.setPivotX(stashedCenterX);
|
||||
view.setPivotY(stashedCenterY);
|
||||
});
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
mRegionSamplingHelper.stopAndDestroy();
|
||||
}
|
||||
|
||||
public MultiValueAlpha getStashedHandleAlpha() {
|
||||
return mTaskbarStashedHandleAlpha;
|
||||
}
|
||||
|
||||
public AnimatedFloat getStashedHandleHintScale() {
|
||||
return mTaskbarStashedHandleHintScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a {@link RevealOutlineAnimation} Animator that updates the stashed handle
|
||||
* shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape
|
||||
* morphs into the size of where the taskbar icons will be.
|
||||
*/
|
||||
public Animator createRevealAnimToIsStashed(boolean isStashed) {
|
||||
final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
|
||||
mStashedHandleRadius, mStashedHandleRadius,
|
||||
mControllers.taskbarViewController.getIconLayoutBounds(), mStashedHandleBounds);
|
||||
|
||||
boolean isReversed = !isStashed;
|
||||
boolean changingDirection = mWasLastRevealAnimReversed != isReversed;
|
||||
mWasLastRevealAnimReversed = isReversed;
|
||||
if (changingDirection) {
|
||||
mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim;
|
||||
}
|
||||
|
||||
ValueAnimator revealAnim = handleRevealProvider.createRevealAnimator(mStashedHandleView,
|
||||
isReversed, mStartProgressForNextRevealAnim);
|
||||
revealAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mStartProgressForNextRevealAnim = ((ValueAnimator) animation).getAnimatedFraction();
|
||||
}
|
||||
});
|
||||
return revealAnim;
|
||||
}
|
||||
|
||||
public void onIsStashed(boolean isStashed) {
|
||||
mRegionSamplingHelper.setWindowVisible(isStashed);
|
||||
if (isStashed) {
|
||||
mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
|
||||
mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion());
|
||||
} else {
|
||||
mRegionSamplingHelper.stop();
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateStashedHandleHintScale() {
|
||||
mStashedHandleView.setScaleX(mTaskbarStashedHandleHintScale.value);
|
||||
mStashedHandleView.setScaleY(mTaskbarStashedHandleHintScale.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when the home button is disabled, so we can hide this handle as well.
|
||||
*/
|
||||
public void setIsHomeButtonDisabled(boolean homeDisabled) {
|
||||
mTaskbarStashedHandleAlpha.getProperty(ALPHA_INDEX_HOME_DISABLED).setValue(
|
||||
homeDisabled ? 0 : 1);
|
||||
}
|
||||
|
||||
public boolean isStashedHandleVisible() {
|
||||
return mStashedHandleView.getVisibility() == View.VISIBLE;
|
||||
}
|
||||
}
|
||||
@@ -17,29 +17,37 @@ package com.android.launcher3.taskbar;
|
||||
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
|
||||
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
|
||||
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
|
||||
import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
|
||||
import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.app.ActivityOptions;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo.Config;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Process;
|
||||
import android.os.SystemProperties;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.Display;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.RoundedCorner;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -47,30 +55,27 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.DragSource;
|
||||
import com.android.launcher3.DropTarget;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.dragndrop.DragController;
|
||||
import com.android.launcher3.dragndrop.DragOptions;
|
||||
import com.android.launcher3.dragndrop.DragView;
|
||||
import com.android.launcher3.dragndrop.DraggableView;
|
||||
import com.android.launcher3.folder.Folder;
|
||||
import com.android.launcher3.folder.FolderIcon;
|
||||
import com.android.launcher3.logger.LauncherAtom;
|
||||
import com.android.launcher3.model.data.FolderInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
|
||||
import com.android.launcher3.touch.ItemClickHandler;
|
||||
import com.android.launcher3.util.PackageManagerHelper;
|
||||
import com.android.launcher3.util.SettingsCache;
|
||||
import com.android.launcher3.util.Themes;
|
||||
import com.android.launcher3.util.TraceHelper;
|
||||
import com.android.launcher3.util.ViewCache;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
import com.android.quickstep.SysUINavigationMode;
|
||||
import com.android.quickstep.SysUINavigationMode.Mode;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.rotation.RotationButtonController;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.WindowManagerWrapper;
|
||||
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
|
||||
|
||||
/**
|
||||
* The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
|
||||
@@ -88,59 +93,97 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
|
||||
private final DeviceProfile mDeviceProfile;
|
||||
private final LayoutInflater mLayoutInflater;
|
||||
private final TaskbarDragLayer mDragLayer;
|
||||
private final TaskbarIconController mIconController;
|
||||
private final MyDragController mDragController;
|
||||
private final TaskbarControllers mControllers;
|
||||
|
||||
private final WindowManager mWindowManager;
|
||||
private final @Nullable RoundedCorner mLeftCorner, mRightCorner;
|
||||
private final int mTaskbarHeightForIme;
|
||||
private WindowManager.LayoutParams mWindowLayoutParams;
|
||||
private boolean mIsFullscreen;
|
||||
// The size we should return to when we call setTaskbarWindowFullscreen(false)
|
||||
private int mLastRequestedNonFullscreenHeight;
|
||||
|
||||
private final SysUINavigationMode.Mode mNavMode;
|
||||
private final TaskbarNavButtonController mNavButtonController;
|
||||
private final ViewCache mViewCache = new ViewCache();
|
||||
|
||||
private final boolean mIsSafeModeEnabled;
|
||||
|
||||
@NonNull
|
||||
private TaskbarUIController mUIController = TaskbarUIController.DEFAULT;
|
||||
|
||||
private final View.OnClickListener mOnTaskbarIconClickListener;
|
||||
private final View.OnLongClickListener mOnTaskbarIconLongClickListener;
|
||||
private final boolean mIsUserSetupComplete;
|
||||
private boolean mIsDestroyed = false;
|
||||
// The flag to know if the window is excluded from magnification region computation.
|
||||
private boolean mIsExcludeFromMagnificationRegion = false;
|
||||
|
||||
public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
|
||||
TaskbarNavButtonController buttonController) {
|
||||
TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
|
||||
unfoldTransitionProgressProvider) {
|
||||
super(windowContext, Themes.getActivityThemeRes(windowContext));
|
||||
mDeviceProfile = dp;
|
||||
mNavButtonController = buttonController;
|
||||
|
||||
mNavMode = SysUINavigationMode.getMode(windowContext);
|
||||
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
|
||||
() -> getPackageManager().isSafeMode());
|
||||
mIsUserSetupComplete = SettingsCache.INSTANCE.get(this).getValue(
|
||||
Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
|
||||
|
||||
mOnTaskbarIconLongClickListener =
|
||||
new TaskbarDragController(this)::startSystemDragOnLongClick;
|
||||
mOnTaskbarIconClickListener = this::onTaskbarIconClicked;
|
||||
|
||||
float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size);
|
||||
final Resources resources = getResources();
|
||||
float taskbarIconSize = resources.getDimension(R.dimen.taskbar_icon_size);
|
||||
mDeviceProfile.updateIconSize(1, resources);
|
||||
float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx;
|
||||
mDeviceProfile.updateIconSize(iconScale, getResources());
|
||||
mDeviceProfile.updateIconSize(iconScale, resources);
|
||||
|
||||
mTaskbarHeightForIme = resources.getDimensionPixelSize(R.dimen.taskbar_ime_size);
|
||||
|
||||
mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
|
||||
mDragLayer = (TaskbarDragLayer) mLayoutInflater
|
||||
.inflate(R.layout.taskbar, null, false);
|
||||
mIconController = new TaskbarIconController(this, mDragLayer);
|
||||
mDragController = new MyDragController(this);
|
||||
|
||||
// Inflate views.
|
||||
mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(
|
||||
R.layout.taskbar, null, false);
|
||||
TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view);
|
||||
TaskbarScrimView taskbarScrimView = mDragLayer.findViewById(R.id.taskbar_scrim);
|
||||
FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
|
||||
StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
|
||||
|
||||
Display display = windowContext.getDisplay();
|
||||
Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
|
||||
? windowContext.getApplicationContext()
|
||||
: windowContext.getApplicationContext().createDisplayContext(display);
|
||||
mWindowManager = c.getSystemService(WindowManager.class);
|
||||
mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
|
||||
mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
|
||||
|
||||
// Construct controllers.
|
||||
mControllers = new TaskbarControllers(this,
|
||||
new TaskbarDragController(this),
|
||||
buttonController,
|
||||
new NavbarButtonsViewController(this, navButtonsView),
|
||||
new RotationButtonController(this,
|
||||
c.getColor(R.color.taskbar_nav_icon_light_color),
|
||||
c.getColor(R.color.taskbar_nav_icon_dark_color),
|
||||
R.drawable.ic_sysbar_rotate_button_ccw_start_0,
|
||||
R.drawable.ic_sysbar_rotate_button_ccw_start_90,
|
||||
R.drawable.ic_sysbar_rotate_button_cw_start_0,
|
||||
R.drawable.ic_sysbar_rotate_button_cw_start_90,
|
||||
() -> getDisplay().getRotation()),
|
||||
new TaskbarDragLayerController(this, mDragLayer),
|
||||
new TaskbarViewController(this, taskbarView),
|
||||
new TaskbarScrimViewController(this, taskbarScrimView),
|
||||
new TaskbarUnfoldAnimationController(unfoldTransitionProgressProvider,
|
||||
mWindowManager),
|
||||
new TaskbarKeyguardController(this),
|
||||
new StashedHandleViewController(this, stashedHandleView),
|
||||
new TaskbarStashController(this),
|
||||
new TaskbarEduController(this),
|
||||
new TaskbarAutohideSuspendController(this),
|
||||
new TaskbarPopupController());
|
||||
}
|
||||
|
||||
public void init() {
|
||||
public void init(TaskbarSharedState sharedState) {
|
||||
mLastRequestedNonFullscreenHeight = getDefaultTaskbarWindowHeight();
|
||||
mWindowLayoutParams = new WindowManager.LayoutParams(
|
||||
MATCH_PARENT,
|
||||
mDeviceProfile.taskbarSize,
|
||||
TYPE_APPLICATION_OVERLAY,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
||||
mLastRequestedNonFullscreenHeight,
|
||||
TYPE_NAVIGATION_BAR_PANEL,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||
| WindowManager.LayoutParams.FLAG_SLIPPERY,
|
||||
PixelFormat.TRANSLUCENT);
|
||||
mWindowLayoutParams.setTitle(WINDOW_TITLE);
|
||||
mWindowLayoutParams.packageName = getPackageName();
|
||||
@@ -148,31 +191,40 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
|
||||
mWindowLayoutParams.setFitInsetsTypes(0);
|
||||
mWindowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
|
||||
mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
|
||||
mWindowLayoutParams.setSystemApplicationOverlay(true);
|
||||
mWindowLayoutParams.privateFlags =
|
||||
WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
|
||||
|
||||
WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance();
|
||||
wmWrapper.setProvidesInsetsTypes(
|
||||
mWindowLayoutParams,
|
||||
new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT }
|
||||
);
|
||||
// Adjust the frame by the rounded corners (ie. leaving just the bar as the inset) when
|
||||
// the IME is showing
|
||||
mWindowLayoutParams.providedInternalImeInsets = Insets.of(0,
|
||||
getDefaultTaskbarWindowHeight() - mTaskbarHeightForIme, 0, 0);
|
||||
|
||||
// Initialize controllers after all are constructed.
|
||||
mControllers.init(sharedState);
|
||||
updateSysuiStateFlags(sharedState.sysuiStateFlags, true /* fromInit */);
|
||||
|
||||
mIconController.init(mOnTaskbarIconClickListener, mOnTaskbarIconLongClickListener);
|
||||
mWindowManager.addView(mDragLayer, mWindowLayoutParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the TaskbarContainer height (pass deviceProfile.taskbarSize to reset).
|
||||
*/
|
||||
public void setTaskbarWindowHeight(int height) {
|
||||
if (mWindowLayoutParams.height == height) {
|
||||
return;
|
||||
}
|
||||
mWindowLayoutParams.height = height;
|
||||
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
|
||||
public void onConfigurationChanged(@Config int configChanges) {
|
||||
mControllers.onConfigurationChanged(configChanges);
|
||||
}
|
||||
|
||||
public boolean canShowNavButtons() {
|
||||
return ENABLE_THREE_BUTTON_TASKBAR && mNavMode == Mode.THREE_BUTTONS;
|
||||
public boolean isThreeButtonNav() {
|
||||
return mNavMode == Mode.THREE_BUTTONS;
|
||||
}
|
||||
|
||||
public int getLeftCornerRadius() {
|
||||
return mLeftCorner == null ? 0 : mLeftCorner.getRadius();
|
||||
}
|
||||
|
||||
public int getRightCornerRadius() {
|
||||
return mRightCorner == null ? 0 : mRightCorner.getRadius();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -192,59 +244,223 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
|
||||
|
||||
@Override
|
||||
public Rect getFolderBoundingBox() {
|
||||
return mDragLayer.getFolderBoundingBox();
|
||||
return mControllers.taskbarDragLayerController.getFolderBoundingBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DragController getDragController() {
|
||||
return mDragController;
|
||||
public TaskbarDragController getDragController() {
|
||||
return mControllers.taskbarDragController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewCache getViewCache() {
|
||||
return mViewCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsIme() {
|
||||
// Currently we don't support IME because we have FLAG_NOT_FOCUSABLE. We can remove that
|
||||
// flag when opening a floating view that needs IME (such as Folder), but then that means
|
||||
// Taskbar will be below IME and thus users can't click the back button.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View.OnClickListener getItemOnClickListener() {
|
||||
return this::onTaskbarIconClicked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change from hotseat/predicted hotseat to taskbar container.
|
||||
*/
|
||||
@Override
|
||||
public void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuilder) {
|
||||
if (!itemInfoBuilder.hasContainerInfo()) {
|
||||
return;
|
||||
}
|
||||
LauncherAtom.ContainerInfo oldContainer = itemInfoBuilder.getContainerInfo();
|
||||
|
||||
if (oldContainer.hasPredictedHotseatContainer()) {
|
||||
LauncherAtom.PredictedHotseatContainer predictedHotseat =
|
||||
oldContainer.getPredictedHotseatContainer();
|
||||
LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
|
||||
LauncherAtom.TaskBarContainer.newBuilder();
|
||||
|
||||
if (predictedHotseat.hasIndex()) {
|
||||
taskbarBuilder.setIndex(predictedHotseat.getIndex());
|
||||
}
|
||||
if (predictedHotseat.hasCardinality()) {
|
||||
taskbarBuilder.setCardinality(predictedHotseat.getCardinality());
|
||||
}
|
||||
|
||||
itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
|
||||
.setTaskBarContainer(taskbarBuilder));
|
||||
} else if (oldContainer.hasHotseat()) {
|
||||
LauncherAtom.HotseatContainer hotseat = oldContainer.getHotseat();
|
||||
LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
|
||||
LauncherAtom.TaskBarContainer.newBuilder();
|
||||
|
||||
if (hotseat.hasIndex()) {
|
||||
taskbarBuilder.setIndex(hotseat.getIndex());
|
||||
}
|
||||
|
||||
itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
|
||||
.setTaskBarContainer(taskbarBuilder));
|
||||
} else if (oldContainer.hasFolder() && oldContainer.getFolder().hasHotseat()) {
|
||||
LauncherAtom.FolderContainer.Builder folderBuilder = oldContainer.getFolder()
|
||||
.toBuilder();
|
||||
LauncherAtom.HotseatContainer hotseat = folderBuilder.getHotseat();
|
||||
LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
|
||||
LauncherAtom.TaskBarContainer.newBuilder();
|
||||
|
||||
if (hotseat.hasIndex()) {
|
||||
taskbarBuilder.setIndex(hotseat.getIndex());
|
||||
}
|
||||
|
||||
folderBuilder.setTaskbar(taskbarBuilder);
|
||||
folderBuilder.clearHotseat();
|
||||
itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
|
||||
.setFolder(folderBuilder));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new data-source for this taskbar instance
|
||||
*/
|
||||
public void setUIController(@NonNull TaskbarUIController uiController) {
|
||||
mUIController.onDestroy();
|
||||
mUIController = uiController;
|
||||
mIconController.setUIController(mUIController);
|
||||
mUIController.onCreate();
|
||||
mControllers.uiController.onDestroy();
|
||||
mControllers.uiController = uiController;
|
||||
mControllers.uiController.init(mControllers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the flag indicating setup UI is visible
|
||||
*/
|
||||
public void setSetupUIVisible(boolean isVisible) {
|
||||
mControllers.taskbarStashController.setSetupUIVisible(isVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this instance of taskbar is no longer needed
|
||||
*/
|
||||
public void onDestroy() {
|
||||
mIsDestroyed = true;
|
||||
setUIController(TaskbarUIController.DEFAULT);
|
||||
mIconController.onDestroy();
|
||||
mControllers.onDestroy();
|
||||
mWindowManager.removeViewImmediate(mDragLayer);
|
||||
}
|
||||
|
||||
void onNavigationButtonClick(@TaskbarButton int buttonType) {
|
||||
mNavButtonController.onButtonClick(buttonType);
|
||||
public void updateSysuiStateFlags(int systemUiStateFlags, boolean fromInit) {
|
||||
mControllers.navbarButtonsViewController.updateStateForSysuiFlags(systemUiStateFlags,
|
||||
fromInit);
|
||||
mControllers.taskbarViewController.setImeIsVisible(
|
||||
mControllers.navbarButtonsViewController.isImeVisible());
|
||||
int shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
|
||||
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
|
||||
onNotificationShadeExpandChanged((systemUiStateFlags & shadeExpandedFlags) != 0, fromInit);
|
||||
mControllers.taskbarViewController.setRecentsButtonDisabled(
|
||||
mControllers.navbarButtonsViewController.isRecentsDisabled());
|
||||
mControllers.stashedHandleViewController.setIsHomeButtonDisabled(
|
||||
mControllers.navbarButtonsViewController.isHomeDisabled());
|
||||
mControllers.taskbarKeyguardController.updateStateForSysuiFlags(systemUiStateFlags);
|
||||
mControllers.taskbarStashController.updateStateForSysuiFlags(systemUiStateFlags, fromInit);
|
||||
mControllers.taskbarScrimViewController.updateStateForSysuiFlags(systemUiStateFlags,
|
||||
fromInit);
|
||||
mControllers.navButtonController.updateSysuiFlags(systemUiStateFlags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly.
|
||||
* Hides the taskbar icons and background when the notication shade is expanded.
|
||||
*/
|
||||
public void setImeIsVisible(boolean isImeVisible) {
|
||||
mIconController.setImeIsVisible(isImeVisible);
|
||||
private void onNotificationShadeExpandChanged(boolean isExpanded, boolean skipAnim) {
|
||||
float alpha = isExpanded ? 0 : 1;
|
||||
AnimatorSet anim = new AnimatorSet();
|
||||
anim.play(mControllers.taskbarViewController.getTaskbarIconAlpha().getProperty(
|
||||
TaskbarViewController.ALPHA_INDEX_NOTIFICATION_EXPANDED).animateToValue(alpha));
|
||||
if (!isThreeButtonNav()) {
|
||||
anim.play(mControllers.taskbarDragLayerController.getNotificationShadeBgTaskbar()
|
||||
.animateToValue(alpha));
|
||||
}
|
||||
anim.start();
|
||||
if (skipAnim) {
|
||||
anim.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from
|
||||
* instantiating at all, which is what's responsible for sending sysui state flags over.
|
||||
*
|
||||
* @param vis IME visibility flag
|
||||
*/
|
||||
public void updateImeStatus(int displayId, int vis, boolean showImeSwitcher) {
|
||||
mIconController.updateImeStatus(displayId, vis, showImeSwitcher);
|
||||
public void onRotationProposal(int rotation, boolean isValid) {
|
||||
mControllers.rotationButtonController.onRotationProposal(rotation, isValid);
|
||||
}
|
||||
|
||||
public void disableNavBarElements(int displayId, int state1, int state2, boolean animate) {
|
||||
if (displayId != getDisplayId()) {
|
||||
return;
|
||||
}
|
||||
mControllers.rotationButtonController.onDisable2FlagChanged(state2);
|
||||
}
|
||||
|
||||
public void onSystemBarAttributesChanged(int displayId, int behavior) {
|
||||
mControllers.rotationButtonController.onBehaviorChanged(displayId, behavior);
|
||||
}
|
||||
|
||||
public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
|
||||
if (!isUserSetupComplete()) {
|
||||
return;
|
||||
}
|
||||
mControllers.navbarButtonsViewController.getTaskbarNavButtonDarkIntensity()
|
||||
.updateValue(darkIntensity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size.
|
||||
*/
|
||||
protected void setTaskbarWindowFullscreen(boolean fullscreen) {
|
||||
setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : getDeviceProfile().taskbarSize);
|
||||
public void setTaskbarWindowFullscreen(boolean fullscreen) {
|
||||
mControllers.taskbarAutohideSuspendController.updateFlag(
|
||||
TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_FULLSCREEN, fullscreen);
|
||||
mIsFullscreen = fullscreen;
|
||||
setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : mLastRequestedNonFullscreenHeight);
|
||||
}
|
||||
|
||||
public boolean isTaskbarWindowFullscreen() {
|
||||
return mIsFullscreen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the TaskbarContainer height (pass {@link #getDefaultTaskbarWindowHeight()} to reset).
|
||||
*/
|
||||
public void setTaskbarWindowHeight(int height) {
|
||||
if (mWindowLayoutParams.height == height || mIsDestroyed) {
|
||||
return;
|
||||
}
|
||||
if (height == MATCH_PARENT) {
|
||||
height = mDeviceProfile.heightPx;
|
||||
} else {
|
||||
mLastRequestedNonFullscreenHeight = height;
|
||||
if (mIsFullscreen) {
|
||||
// We still need to be fullscreen, so defer any change to our height until we call
|
||||
// setTaskbarWindowFullscreen(false). For example, this could happen when dragging
|
||||
// from the gesture region, as the drag will cancel the gesture and reset launcher's
|
||||
// state, which in turn normally would reset the taskbar window height as well.
|
||||
return;
|
||||
}
|
||||
}
|
||||
mWindowLayoutParams.height = height;
|
||||
mWindowLayoutParams.providedInternalImeInsets =
|
||||
Insets.of(0, height - mTaskbarHeightForIme, 0, 0);
|
||||
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default height of the window, including the static corner radii above taskbar.
|
||||
*/
|
||||
public int getDefaultTaskbarWindowHeight() {
|
||||
return mDeviceProfile.taskbarSize + Math.max(getLeftCornerRadius(), getRightCornerRadius());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bottom insets taskbar provides to the IME when IME is visible.
|
||||
*/
|
||||
public int getTaskbarHeightForIme() {
|
||||
return mTaskbarHeightForIme;
|
||||
}
|
||||
|
||||
protected void onTaskbarIconClicked(View view) {
|
||||
@@ -260,10 +476,11 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
|
||||
|
||||
getDragLayer().post(() -> {
|
||||
folder.animateOpen();
|
||||
getStatsLogManager().logger().withItemInfo(folder.mInfo).log(LAUNCHER_FOLDER_OPEN);
|
||||
|
||||
folder.iterateOverItems((itemInfo, itemView) -> {
|
||||
itemView.setOnClickListener(mOnTaskbarIconClickListener);
|
||||
itemView.setOnLongClickListener(mOnTaskbarIconLongClickListener);
|
||||
mControllers.taskbarViewController
|
||||
.setClickAndLongClickListenersForIcon(itemView);
|
||||
// To play haptic when dragging, like other Taskbar items do.
|
||||
itemView.setHapticFeedbackEnabled(true);
|
||||
return false;
|
||||
@@ -271,7 +488,9 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
|
||||
});
|
||||
} else if (tag instanceof WorkspaceItemInfo) {
|
||||
WorkspaceItemInfo info = (WorkspaceItemInfo) tag;
|
||||
if (!(info.isDisabled() && ItemClickHandler.handleDisabledItemClicked(info, this))) {
|
||||
if (info.isDisabled()) {
|
||||
ItemClickHandler.handleDisabledItemClicked(info, this);
|
||||
} else {
|
||||
Intent intent = new Intent(info.getIntent())
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
try {
|
||||
@@ -295,6 +514,8 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
|
||||
getSystemService(LauncherApps.class).startMainActivity(
|
||||
intent.getComponent(), info.user, intent.getSourceBounds(), null);
|
||||
}
|
||||
|
||||
mControllers.uiController.onTaskbarIconLaunched(info);
|
||||
} catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
|
||||
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
@@ -308,26 +529,44 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
|
||||
AbstractFloatingView.closeAllOpenViews(this);
|
||||
}
|
||||
|
||||
private static class MyDragController extends DragController<TaskbarActivityContext> {
|
||||
MyDragController(TaskbarActivityContext activity) {
|
||||
super(activity);
|
||||
/**
|
||||
* Called when we detect a long press in the nav region before passing the gesture slop.
|
||||
* @return Whether taskbar handled the long press, and thus should cancel the gesture.
|
||||
*/
|
||||
public boolean onLongPressToUnstashTaskbar() {
|
||||
return mControllers.taskbarStashController.onLongPressToUnstashTaskbar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we detect a motion down or up/cancel in the nav region while stashed.
|
||||
* @param animateForward Whether to animate towards the unstashed hint state or back to stashed.
|
||||
*/
|
||||
public void startTaskbarUnstashHint(boolean animateForward) {
|
||||
mControllers.taskbarStashController.startUnstashHint(animateForward);
|
||||
}
|
||||
|
||||
protected boolean isUserSetupComplete() {
|
||||
return mIsUserSetupComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we determine the touchable region.
|
||||
*
|
||||
* @param exclude {@code true} then the magnification region computation will omit the window.
|
||||
*/
|
||||
public void excludeFromMagnificationRegion(boolean exclude) {
|
||||
if (mIsExcludeFromMagnificationRegion == exclude) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DragView startDrag(@Nullable Drawable drawable, @Nullable View view,
|
||||
DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source,
|
||||
ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale,
|
||||
float dragViewScaleOnDrop, DragOptions options) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void exitDrag() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DropTarget getDefaultDropTarget(int[] dropCoordinates) {
|
||||
return null;
|
||||
mIsExcludeFromMagnificationRegion = exclude;
|
||||
if (exclude) {
|
||||
mWindowLayoutParams.privateFlags |=
|
||||
WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
|
||||
} else {
|
||||
mWindowLayoutParams.privateFlags &=
|
||||
~WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
|
||||
}
|
||||
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import static com.android.launcher3.LauncherState.TASKBAR;
|
||||
|
||||
import android.animation.Animator;
|
||||
|
||||
import com.android.launcher3.BaseQuickstepLauncher;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.taskbar.LauncherTaskbarUIController.TaskbarAnimationControllerCallbacks;
|
||||
import com.android.quickstep.AnimatedFloat;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.systemui.shared.system.QuickStepContract;
|
||||
|
||||
/**
|
||||
* Works with TaskbarController to update the TaskbarView's visual properties based on factors such
|
||||
* as LauncherState, whether Launcher is in the foreground, etc.
|
||||
*/
|
||||
public class TaskbarAnimationController {
|
||||
|
||||
private static final long IME_VISIBILITY_ALPHA_DURATION = 120;
|
||||
|
||||
private final BaseQuickstepLauncher mLauncher;
|
||||
private final TaskbarAnimationControllerCallbacks mTaskbarCallbacks;
|
||||
|
||||
// Background alpha.
|
||||
private final AnimatedFloat mTaskbarBackgroundAlpha = new AnimatedFloat(
|
||||
this::onTaskbarBackgroundAlphaChanged);
|
||||
|
||||
// Overall visibility.
|
||||
private final AnimatedFloat mTaskbarVisibilityAlphaForLauncherState = new AnimatedFloat(
|
||||
this::updateVisibilityAlpha);
|
||||
private final AnimatedFloat mTaskbarVisibilityAlphaForIme = new AnimatedFloat(
|
||||
this::updateVisibilityAlphaForIme);
|
||||
|
||||
// Scale.
|
||||
private final AnimatedFloat mTaskbarScaleForLauncherState = new AnimatedFloat(
|
||||
this::updateScale);
|
||||
|
||||
// TranslationY.
|
||||
private final AnimatedFloat mTaskbarTranslationYForLauncherState = new AnimatedFloat(
|
||||
this::updateTranslationY);
|
||||
|
||||
public TaskbarAnimationController(BaseQuickstepLauncher launcher,
|
||||
TaskbarAnimationControllerCallbacks taskbarCallbacks) {
|
||||
mLauncher = launcher;
|
||||
mTaskbarCallbacks = taskbarCallbacks;
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
mTaskbarBackgroundAlpha.updateValue(mLauncher.hasBeenResumed() ? 0f : 1f);
|
||||
boolean isVisibleForLauncherState = (mLauncher.getStateManager().getState()
|
||||
.getVisibleElements(mLauncher) & TASKBAR) != 0;
|
||||
mTaskbarVisibilityAlphaForLauncherState.updateValue(isVisibleForLauncherState ? 1f : 0f);
|
||||
boolean isImeVisible = (SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags()
|
||||
& QuickStepContract.SYSUI_STATE_IME_SHOWING) != 0;
|
||||
mTaskbarVisibilityAlphaForIme.updateValue(isImeVisible ? 0f : 1f);
|
||||
|
||||
onTaskbarBackgroundAlphaChanged();
|
||||
updateVisibilityAlpha();
|
||||
}
|
||||
|
||||
protected void cleanup() {
|
||||
setNavBarButtonAlpha(1f);
|
||||
}
|
||||
|
||||
protected AnimatedFloat getTaskbarVisibilityForLauncherState() {
|
||||
return mTaskbarVisibilityAlphaForLauncherState;
|
||||
}
|
||||
|
||||
protected AnimatedFloat getTaskbarScaleForLauncherState() {
|
||||
return mTaskbarScaleForLauncherState;
|
||||
}
|
||||
|
||||
protected AnimatedFloat getTaskbarTranslationYForLauncherState() {
|
||||
return mTaskbarTranslationYForLauncherState;
|
||||
}
|
||||
|
||||
protected Animator createAnimToBackgroundAlpha(float toAlpha, long duration) {
|
||||
return mTaskbarBackgroundAlpha.animateToValue(mTaskbarBackgroundAlpha.value, toAlpha)
|
||||
.setDuration(duration);
|
||||
}
|
||||
|
||||
protected void animateToVisibilityForIme(float toAlpha) {
|
||||
mTaskbarVisibilityAlphaForIme.animateToValue(mTaskbarVisibilityAlphaForIme.value, toAlpha)
|
||||
.setDuration(IME_VISIBILITY_ALPHA_DURATION).start();
|
||||
}
|
||||
|
||||
private void onTaskbarBackgroundAlphaChanged() {
|
||||
mTaskbarCallbacks.updateTaskbarBackgroundAlpha(mTaskbarBackgroundAlpha.value);
|
||||
updateVisibilityAlpha();
|
||||
updateScale();
|
||||
updateTranslationY();
|
||||
}
|
||||
|
||||
private void updateVisibilityAlpha() {
|
||||
// We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
|
||||
// assumption being that Taskbar should always be visible regardless of the current
|
||||
// LauncherState if Launcher is paused.
|
||||
float alphaDueToIme = mTaskbarVisibilityAlphaForIme.value;
|
||||
float alphaDueToLauncher = Math.max(mTaskbarBackgroundAlpha.value,
|
||||
mTaskbarVisibilityAlphaForLauncherState.value);
|
||||
float taskbarAlpha = alphaDueToLauncher * alphaDueToIme;
|
||||
mTaskbarCallbacks.updateTaskbarVisibilityAlpha(taskbarAlpha);
|
||||
|
||||
// Make the nav bar invisible if taskbar is visible.
|
||||
setNavBarButtonAlpha(1f - taskbarAlpha);
|
||||
}
|
||||
|
||||
private void updateVisibilityAlphaForIme() {
|
||||
updateVisibilityAlpha();
|
||||
float taskbarAlphaDueToIme = mTaskbarVisibilityAlphaForIme.value;
|
||||
mTaskbarCallbacks.updateImeBarVisibilityAlpha(1f - taskbarAlphaDueToIme);
|
||||
}
|
||||
|
||||
private void updateScale() {
|
||||
// We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
|
||||
// assumption being that Taskbar should always be at scale 1f regardless of the current
|
||||
// LauncherState if Launcher is paused.
|
||||
float scale = mTaskbarScaleForLauncherState.value;
|
||||
scale = Utilities.mapRange(mTaskbarBackgroundAlpha.value, scale, 1f);
|
||||
mTaskbarCallbacks.updateTaskbarScale(scale);
|
||||
}
|
||||
|
||||
private void updateTranslationY() {
|
||||
// We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
|
||||
// assumption being that Taskbar should always be at translationY 0f regardless of the
|
||||
// current LauncherState if Launcher is paused.
|
||||
float translationY = mTaskbarTranslationYForLauncherState.value;
|
||||
translationY = Utilities.mapRange(mTaskbarBackgroundAlpha.value, translationY, 0f);
|
||||
mTaskbarCallbacks.updateTaskbarTranslationY(translationY);
|
||||
}
|
||||
|
||||
private void setNavBarButtonAlpha(float navBarAlpha) {
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).setNavBarButtonAlpha(navBarAlpha, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Normally Taskbar will auto-hide when entering immersive (fullscreen) apps. This controller allows
|
||||
* us to suspend that behavior in certain cases (e.g. opening a Folder or dragging an icon).
|
||||
*/
|
||||
public class TaskbarAutohideSuspendController {
|
||||
|
||||
public static final int FLAG_AUTOHIDE_SUSPEND_FULLSCREEN = 1 << 0;
|
||||
public static final int FLAG_AUTOHIDE_SUSPEND_DRAGGING = 1 << 1;
|
||||
@IntDef(flag = true, value = {
|
||||
FLAG_AUTOHIDE_SUSPEND_FULLSCREEN,
|
||||
FLAG_AUTOHIDE_SUSPEND_DRAGGING,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface AutohideSuspendFlag {}
|
||||
|
||||
private final SystemUiProxy mSystemUiProxy;
|
||||
|
||||
private @AutohideSuspendFlag int mAutohideSuspendFlags = 0;
|
||||
|
||||
public TaskbarAutohideSuspendController(TaskbarActivityContext activity) {
|
||||
mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
mSystemUiProxy.notifyTaskbarAutohideSuspend(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or removes the given flag, then notifies system UI proxy whether to suspend auto-hide.
|
||||
*/
|
||||
public void updateFlag(@AutohideSuspendFlag int flag, boolean enabled) {
|
||||
if (enabled) {
|
||||
mAutohideSuspendFlags |= flag;
|
||||
} else {
|
||||
mAutohideSuspendFlags &= ~flag;
|
||||
}
|
||||
mSystemUiProxy.notifyTaskbarAutohideSuspend(mAutohideSuspendFlags != 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import android.content.pm.ActivityInfo.Config;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.systemui.shared.rotation.RotationButtonController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Hosts various taskbar controllers to facilitate passing between one another.
|
||||
*/
|
||||
public class TaskbarControllers {
|
||||
|
||||
public final TaskbarActivityContext taskbarActivityContext;
|
||||
public final TaskbarDragController taskbarDragController;
|
||||
public final TaskbarNavButtonController navButtonController;
|
||||
public final NavbarButtonsViewController navbarButtonsViewController;
|
||||
public final RotationButtonController rotationButtonController;
|
||||
public final TaskbarDragLayerController taskbarDragLayerController;
|
||||
public final TaskbarScrimViewController taskbarScrimViewController;
|
||||
public final TaskbarViewController taskbarViewController;
|
||||
public final TaskbarUnfoldAnimationController taskbarUnfoldAnimationController;
|
||||
public final TaskbarKeyguardController taskbarKeyguardController;
|
||||
public final StashedHandleViewController stashedHandleViewController;
|
||||
public final TaskbarStashController taskbarStashController;
|
||||
public final TaskbarEduController taskbarEduController;
|
||||
public final TaskbarAutohideSuspendController taskbarAutohideSuspendController;
|
||||
public final TaskbarPopupController taskbarPopupController;
|
||||
|
||||
/** Do not store this controller, as it may change at runtime. */
|
||||
@NonNull public TaskbarUIController uiController = TaskbarUIController.DEFAULT;
|
||||
|
||||
private boolean mAreAllControllersInitialized;
|
||||
private final List<Runnable> mPostInitCallbacks = new ArrayList<>();
|
||||
|
||||
public TaskbarControllers(TaskbarActivityContext taskbarActivityContext,
|
||||
TaskbarDragController taskbarDragController,
|
||||
TaskbarNavButtonController navButtonController,
|
||||
NavbarButtonsViewController navbarButtonsViewController,
|
||||
RotationButtonController rotationButtonController,
|
||||
TaskbarDragLayerController taskbarDragLayerController,
|
||||
TaskbarViewController taskbarViewController,
|
||||
TaskbarScrimViewController taskbarScrimViewController,
|
||||
TaskbarUnfoldAnimationController taskbarUnfoldAnimationController,
|
||||
TaskbarKeyguardController taskbarKeyguardController,
|
||||
StashedHandleViewController stashedHandleViewController,
|
||||
TaskbarStashController taskbarStashController,
|
||||
TaskbarEduController taskbarEduController,
|
||||
TaskbarAutohideSuspendController taskbarAutoHideSuspendController,
|
||||
TaskbarPopupController taskbarPopupController) {
|
||||
this.taskbarActivityContext = taskbarActivityContext;
|
||||
this.taskbarDragController = taskbarDragController;
|
||||
this.navButtonController = navButtonController;
|
||||
this.navbarButtonsViewController = navbarButtonsViewController;
|
||||
this.rotationButtonController = rotationButtonController;
|
||||
this.taskbarDragLayerController = taskbarDragLayerController;
|
||||
this.taskbarViewController = taskbarViewController;
|
||||
this.taskbarScrimViewController = taskbarScrimViewController;
|
||||
this.taskbarUnfoldAnimationController = taskbarUnfoldAnimationController;
|
||||
this.taskbarKeyguardController = taskbarKeyguardController;
|
||||
this.stashedHandleViewController = stashedHandleViewController;
|
||||
this.taskbarStashController = taskbarStashController;
|
||||
this.taskbarEduController = taskbarEduController;
|
||||
this.taskbarAutohideSuspendController = taskbarAutoHideSuspendController;
|
||||
this.taskbarPopupController = taskbarPopupController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all controllers. Note that controllers can now reference each other through this
|
||||
* TaskbarControllers instance, but should be careful to only access things that were created
|
||||
* in constructors for now, as some controllers may still be waiting for init().
|
||||
*/
|
||||
public void init(TaskbarSharedState sharedState) {
|
||||
mAreAllControllersInitialized = false;
|
||||
|
||||
taskbarDragController.init(this);
|
||||
navbarButtonsViewController.init(this);
|
||||
rotationButtonController.init();
|
||||
taskbarDragLayerController.init(this);
|
||||
taskbarViewController.init(this);
|
||||
taskbarScrimViewController.init(this);
|
||||
taskbarUnfoldAnimationController.init(this);
|
||||
taskbarKeyguardController.init(navbarButtonsViewController);
|
||||
stashedHandleViewController.init(this);
|
||||
taskbarStashController.init(this, sharedState);
|
||||
taskbarEduController.init(this);
|
||||
|
||||
mAreAllControllersInitialized = true;
|
||||
for (Runnable postInitCallback : mPostInitCallbacks) {
|
||||
postInitCallback.run();
|
||||
}
|
||||
mPostInitCallbacks.clear();
|
||||
}
|
||||
|
||||
public void onConfigurationChanged(@Config int configChanges) {
|
||||
navbarButtonsViewController.onConfigurationChanged(configChanges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up all controllers.
|
||||
*/
|
||||
public void onDestroy() {
|
||||
navbarButtonsViewController.onDestroy();
|
||||
uiController.onDestroy();
|
||||
rotationButtonController.onDestroy();
|
||||
taskbarDragLayerController.onDestroy();
|
||||
taskbarKeyguardController.onDestroy();
|
||||
taskbarUnfoldAnimationController.onDestroy();
|
||||
taskbarViewController.onDestroy();
|
||||
stashedHandleViewController.onDestroy();
|
||||
taskbarAutohideSuspendController.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* If all controllers are already initialized, runs the given callback immediately. Otherwise,
|
||||
* queues it to run after calling init() on all controllers. This should likely be used in any
|
||||
* case where one controller is telling another controller to do something inside init().
|
||||
*/
|
||||
public void runAfterInit(Runnable callback) {
|
||||
if (mAreAllControllersInitialized) {
|
||||
callback.run();
|
||||
} else {
|
||||
mPostInitCallbacks.add(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,25 +15,42 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipDescription;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.UserHandle;
|
||||
import android.view.DragEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.logging.InstanceId;
|
||||
import com.android.internal.logging.InstanceIdSequence;
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.DragSource;
|
||||
import com.android.launcher3.DropTarget;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.dragndrop.DragController;
|
||||
import com.android.launcher3.dragndrop.DragDriver;
|
||||
import com.android.launcher3.dragndrop.DragOptions;
|
||||
import com.android.launcher3.dragndrop.DragView;
|
||||
import com.android.launcher3.dragndrop.DraggableView;
|
||||
import com.android.launcher3.graphics.DragPreviewProvider;
|
||||
import com.android.launcher3.logging.StatsLogManager;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.popup.PopupContainerWithArrow;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.system.ClipDescriptionCompat;
|
||||
import com.android.systemui.shared.system.LauncherAppsCompat;
|
||||
@@ -41,52 +58,213 @@ import com.android.systemui.shared.system.LauncherAppsCompat;
|
||||
/**
|
||||
* Handles long click on Taskbar items to start a system drag and drop operation.
|
||||
*/
|
||||
public class TaskbarDragController {
|
||||
public class TaskbarDragController extends DragController<TaskbarActivityContext> {
|
||||
|
||||
private final Context mContext;
|
||||
private final int mDragIconSize;
|
||||
private final int[] mTempXY = new int[2];
|
||||
|
||||
public TaskbarDragController(Context context) {
|
||||
mContext = context;
|
||||
Resources resources = mContext.getResources();
|
||||
// Initialized in init.
|
||||
TaskbarControllers mControllers;
|
||||
|
||||
// Where the initial touch was relative to the dragged icon.
|
||||
private int mRegistrationX;
|
||||
private int mRegistrationY;
|
||||
|
||||
private boolean mIsSystemDragInProgress;
|
||||
|
||||
public TaskbarDragController(TaskbarActivityContext activity) {
|
||||
super(activity);
|
||||
Resources resources = mActivity.getResources();
|
||||
mDragIconSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_drag_icon_size);
|
||||
}
|
||||
|
||||
public void init(TaskbarControllers controllers) {
|
||||
mControllers = controllers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to start a system drag and drop operation for the given View, using its tag to
|
||||
* generate the ClipDescription and Intent.
|
||||
* @return Whether {@link View#startDragAndDrop} started successfully.
|
||||
*/
|
||||
protected boolean startSystemDragOnLongClick(View view) {
|
||||
protected boolean startDragOnLongClick(View view) {
|
||||
if (!(view instanceof BubbleTextView)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BubbleTextView btv = (BubbleTextView) view;
|
||||
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) {
|
||||
|
||||
mActivity.setTaskbarWindowFullscreen(true);
|
||||
btv.post(() -> {
|
||||
startInternalDrag(btv);
|
||||
btv.getIcon().setIsDisabled(true);
|
||||
mControllers.taskbarAutohideSuspendController.updateFlag(
|
||||
TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, true);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private void startInternalDrag(BubbleTextView btv) {
|
||||
float iconScale = btv.getIcon().getAnimatedScale();
|
||||
|
||||
// Clear the pressed state if necessary
|
||||
btv.clearFocus();
|
||||
btv.setPressed(false);
|
||||
btv.clearPressedBackground();
|
||||
|
||||
final DragPreviewProvider previewProvider = new DragPreviewProvider(btv);
|
||||
final Drawable drawable = previewProvider.createDrawable();
|
||||
final float scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
|
||||
int dragLayerX = mTempXY[0];
|
||||
int dragLayerY = mTempXY[1];
|
||||
|
||||
Rect dragRect = new Rect();
|
||||
btv.getSourceVisualDragBounds(dragRect);
|
||||
dragLayerY += dragRect.top;
|
||||
|
||||
DragOptions dragOptions = new DragOptions();
|
||||
dragOptions.preDragCondition = new DragOptions.PreDragCondition() {
|
||||
private DragView mDragView;
|
||||
|
||||
@Override
|
||||
public boolean shouldStartDrag(double distanceDragged) {
|
||||
return mDragView != null && mDragView.isAnimationFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreDragStart(DropTarget.DragObject dragObject) {
|
||||
mDragView = dragObject.dragView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) {
|
||||
mDragView = null;
|
||||
}
|
||||
};
|
||||
if (FeatureFlags.ENABLE_TASKBAR_POPUP_MENU.get()) {
|
||||
PopupContainerWithArrow<TaskbarActivityContext> popupContainer =
|
||||
mControllers.taskbarPopupController.showForIcon(btv);
|
||||
if (popupContainer != null) {
|
||||
dragOptions.preDragCondition = popupContainer.createPreDragCondition();
|
||||
}
|
||||
}
|
||||
|
||||
startDrag(
|
||||
drawable,
|
||||
/* view = */ null,
|
||||
/* originalView = */ btv,
|
||||
dragLayerX,
|
||||
dragLayerY,
|
||||
(View target, DropTarget.DragObject d, boolean success) -> {} /* DragSource */,
|
||||
(WorkspaceItemInfo) btv.getTag(),
|
||||
/* dragVisualizeOffset = */ null,
|
||||
dragRect,
|
||||
scale * iconScale,
|
||||
scale,
|
||||
dragOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DragView startDrag(@Nullable Drawable drawable, @Nullable View view,
|
||||
DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source,
|
||||
ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale,
|
||||
float dragViewScaleOnDrop, DragOptions options) {
|
||||
mOptions = options;
|
||||
|
||||
mRegistrationX = mMotionDown.x - dragLayerX;
|
||||
mRegistrationY = mMotionDown.y - dragLayerY;
|
||||
|
||||
final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
|
||||
final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
|
||||
|
||||
mLastDropTarget = null;
|
||||
|
||||
mDragObject = new DropTarget.DragObject(mActivity.getApplicationContext());
|
||||
mDragObject.originalView = originalView;
|
||||
mDragObject.deferDragViewCleanupPostAnimation = false;
|
||||
|
||||
mIsInPreDrag = mOptions.preDragCondition != null
|
||||
&& !mOptions.preDragCondition.shouldStartDrag(0);
|
||||
|
||||
float scalePx = mDragIconSize - dragRegion.width();
|
||||
final DragView dragView = mDragObject.dragView = new TaskbarDragView(
|
||||
mActivity,
|
||||
drawable,
|
||||
mRegistrationX,
|
||||
mRegistrationY,
|
||||
initialDragViewScale,
|
||||
dragViewScaleOnDrop,
|
||||
scalePx);
|
||||
dragView.setItemInfo(dragInfo);
|
||||
mDragObject.dragComplete = false;
|
||||
|
||||
mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft);
|
||||
mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop);
|
||||
|
||||
mDragDriver = DragDriver.create(this, mOptions, /* secondaryEventConsumer = */ ev -> {});
|
||||
if (!mOptions.isAccessibleDrag) {
|
||||
mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
|
||||
}
|
||||
|
||||
mDragObject.dragSource = source;
|
||||
mDragObject.dragInfo = dragInfo;
|
||||
mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy();
|
||||
|
||||
if (dragRegion != null) {
|
||||
dragView.setDragRegion(new Rect(dragRegion));
|
||||
}
|
||||
|
||||
dragView.show(mLastTouch.x, mLastTouch.y);
|
||||
mDistanceSinceScroll = 0;
|
||||
|
||||
if (!mIsInPreDrag) {
|
||||
callOnDragStart();
|
||||
} else if (mOptions.preDragCondition != null) {
|
||||
mOptions.preDragCondition.onPreDragStart(mDragObject);
|
||||
}
|
||||
|
||||
handleMoveEvent(mLastTouch.x, mLastTouch.y);
|
||||
|
||||
return dragView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void callOnDragStart() {
|
||||
super.callOnDragStart();
|
||||
// Pre-drag has ended, start the global system drag.
|
||||
AbstractFloatingView.closeAllOpenViews(mActivity);
|
||||
startSystemDrag((BubbleTextView) mDragObject.originalView);
|
||||
}
|
||||
|
||||
private void startSystemDrag(BubbleTextView btv) {
|
||||
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(btv) {
|
||||
|
||||
@Override
|
||||
public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
|
||||
shadowSize.set(mDragIconSize, mDragIconSize);
|
||||
// TODO: should be based on last touch point on the icon.
|
||||
shadowTouchPoint.set(shadowSize.x / 2, shadowSize.y / 2);
|
||||
// The registration point was taken before the icon scaled to mDragIconSize, so
|
||||
// offset the registration to where the touch is on the new size.
|
||||
int offsetX = (mDragIconSize - mDragObject.dragView.getDragRegionWidth()) / 2;
|
||||
int offsetY = (mDragIconSize - mDragObject.dragView.getDragRegionHeight()) / 2;
|
||||
shadowTouchPoint.set(mRegistrationX + offsetX, mRegistrationY + offsetY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawShadow(Canvas canvas) {
|
||||
canvas.save();
|
||||
float scale = (float) mDragIconSize / btv.getIconSize();
|
||||
float scale = mDragObject.dragView.getScaleX();
|
||||
canvas.scale(scale, scale);
|
||||
btv.getIcon().draw(canvas);
|
||||
mDragObject.dragView.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
};
|
||||
|
||||
Object tag = view.getTag();
|
||||
Object tag = btv.getTag();
|
||||
ClipDescription clipDescription = null;
|
||||
Intent intent = null;
|
||||
if (tag instanceof WorkspaceItemInfo) {
|
||||
WorkspaceItemInfo item = (WorkspaceItemInfo) tag;
|
||||
LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
|
||||
LauncherApps launcherApps = mActivity.getSystemService(LauncherApps.class);
|
||||
clipDescription = new ClipDescription(item.title,
|
||||
new String[] {
|
||||
item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
|
||||
@@ -115,29 +293,97 @@ public class TaskbarDragController {
|
||||
}
|
||||
|
||||
if (clipDescription != null && intent != null) {
|
||||
// Need to share the same InstanceId between launcher3 and WM Shell (internal).
|
||||
InstanceId internalInstanceId = new InstanceIdSequence(
|
||||
com.android.launcher3.logging.InstanceId.INSTANCE_ID_MAX).newInstanceId();
|
||||
com.android.launcher3.logging.InstanceId launcherInstanceId =
|
||||
new com.android.launcher3.logging.InstanceId(internalInstanceId.getId());
|
||||
|
||||
intent.putExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID, internalInstanceId);
|
||||
|
||||
ClipData clipData = new ClipData(clipDescription, new ClipData.Item(intent));
|
||||
view.setOnDragListener(getDraggedViewDragListener());
|
||||
return view.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
|
||||
View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE);
|
||||
if (btv.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
|
||||
View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE)) {
|
||||
onSystemDragStarted();
|
||||
|
||||
mActivity.getStatsLogManager().logger().withItemInfo(mDragObject.dragInfo)
|
||||
.withInstanceId(launcherInstanceId)
|
||||
.log(StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the original Taskbar item while it is being dragged.
|
||||
*/
|
||||
private View.OnDragListener getDraggedViewDragListener() {
|
||||
return (view, dragEvent) -> {
|
||||
private void onSystemDragStarted() {
|
||||
mIsSystemDragInProgress = true;
|
||||
mActivity.getDragLayer().setOnDragListener((view, dragEvent) -> {
|
||||
switch (dragEvent.getAction()) {
|
||||
case DragEvent.ACTION_DRAG_STARTED:
|
||||
view.setVisibility(INVISIBLE);
|
||||
// Return true to tell system we are interested in events, so we get DRAG_ENDED.
|
||||
return true;
|
||||
case DragEvent.ACTION_DRAG_ENDED:
|
||||
view.setVisibility(VISIBLE);
|
||||
view.setOnDragListener(null);
|
||||
mIsSystemDragInProgress = false;
|
||||
maybeOnDragEnd();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDragging() {
|
||||
return super.isDragging() || mIsSystemDragInProgress;
|
||||
}
|
||||
|
||||
private void maybeOnDragEnd() {
|
||||
if (!isDragging()) {
|
||||
((BubbleTextView) mDragObject.originalView).getIcon().setIsDisabled(false);
|
||||
mControllers.taskbarAutohideSuspendController.updateFlag(
|
||||
TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void callOnDragEnd() {
|
||||
super.callOnDragEnd();
|
||||
maybeOnDragEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getX(MotionEvent ev) {
|
||||
// We will resize to fill the screen while dragging, so use screen coordinates. This ensures
|
||||
// we start at the correct position even though touch down is on the smaller DragLayer size.
|
||||
return ev.getRawX();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getY(MotionEvent ev) {
|
||||
// We will resize to fill the screen while dragging, so use screen coordinates. This ensures
|
||||
// we start at the correct position even though touch down is on the smaller DragLayer size.
|
||||
return ev.getRawY();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point getClampedDragLayerPos(float x, float y) {
|
||||
// No need to clamp, as we will take up the entire screen.
|
||||
mTmpPoint.set(Math.round(x), Math.round(y));
|
||||
return mTmpPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void exitDrag() {
|
||||
if (mDragObject != null) {
|
||||
mActivity.getDragLayer().removeView(mDragObject.dragView);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDropTarget(DropTarget target) {
|
||||
// No-op as Taskbar currently doesn't support any drop targets internally.
|
||||
// Note: if we do add internal DropTargets, we'll still need to ignore Folder.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DropTarget getDefaultDropTarget(int[] dropCoordinates) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,17 @@ package com.android.launcher3.taskbar;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Path;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.TestProtocol;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
import com.android.systemui.shared.system.ViewTreeObserverWrapper;
|
||||
@@ -37,14 +40,16 @@ import com.android.systemui.shared.system.ViewTreeObserverWrapper.OnComputeInset
|
||||
*/
|
||||
public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
|
||||
private final int mFolderMargin;
|
||||
private final Paint mTaskbarBackgroundPaint;
|
||||
|
||||
private TaskbarIconController.Callbacks mControllerCallbacks;
|
||||
private TaskbarView mTaskbarView;
|
||||
|
||||
private final Path mInvertedLeftCornerPath, mInvertedRightCornerPath;
|
||||
private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets;
|
||||
|
||||
// Initialized in init.
|
||||
private TaskbarDragLayerController.TaskbarDragLayerCallbacks mControllerCallbacks;
|
||||
private float mLeftCornerRadius, mRightCornerRadius;
|
||||
|
||||
private float mTaskbarBackgroundOffset;
|
||||
|
||||
public TaskbarDragLayer(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@@ -61,25 +66,47 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs,
|
||||
int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, 1 /* alphaChannelCount */);
|
||||
mFolderMargin = getResources().getDimensionPixelSize(R.dimen.taskbar_folder_margin);
|
||||
mTaskbarBackgroundPaint = new Paint();
|
||||
mTaskbarBackgroundPaint.setColor(getResources().getColor(R.color.taskbar_background));
|
||||
mTaskbarBackgroundPaint.setAlpha(0);
|
||||
mTaskbarBackgroundPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
|
||||
mTaskbarBackgroundPaint.setStyle(Paint.Style.FILL);
|
||||
|
||||
// Will be set in init(), but this ensures they are always non-null.
|
||||
mInvertedLeftCornerPath = new Path();
|
||||
mInvertedRightCornerPath = new Path();
|
||||
}
|
||||
|
||||
public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
|
||||
mControllerCallbacks = callbacks;
|
||||
|
||||
// Create the paths for the inverted rounded corners above the taskbar. Start with a filled
|
||||
// square, and then subtracting out a circle from the appropriate corner.
|
||||
mLeftCornerRadius = mActivity.getLeftCornerRadius();
|
||||
mRightCornerRadius = mActivity.getRightCornerRadius();
|
||||
Path square = new Path();
|
||||
square.addRect(0, 0, mLeftCornerRadius, mLeftCornerRadius, Path.Direction.CW);
|
||||
Path circle = new Path();
|
||||
circle.addCircle(mLeftCornerRadius, 0, mLeftCornerRadius, Path.Direction.CW);
|
||||
mInvertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE);
|
||||
square.reset();
|
||||
square.addRect(0, 0, mRightCornerRadius, mRightCornerRadius, Path.Direction.CW);
|
||||
circle.reset();
|
||||
circle.addCircle(0, 0, mRightCornerRadius, Path.Direction.CW);
|
||||
mInvertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE);
|
||||
|
||||
recreateControllers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recreateControllers() {
|
||||
mControllers = new TouchController[0];
|
||||
}
|
||||
|
||||
public void init(TaskbarIconController.Callbacks callbacks, TaskbarView taskbarView) {
|
||||
mControllerCallbacks = callbacks;
|
||||
mTaskbarView = taskbarView;
|
||||
mControllers = new TouchController[] {mActivity.getDragController()};
|
||||
}
|
||||
|
||||
private void onComputeTaskbarInsets(InsetsInfo insetsInfo) {
|
||||
if (mControllerCallbacks != null) {
|
||||
mControllerCallbacks.updateInsetsTouchability(insetsInfo);
|
||||
mControllerCallbacks.updateContentInsets(insetsInfo.contentInsets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,12 +135,6 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void updateImeBarVisibilityAlpha(float alpha) {
|
||||
if (mControllerCallbacks != null) {
|
||||
mControllerCallbacks.updateImeBarVisibilityAlpha(alpha);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewRemoved(View child) {
|
||||
super.onViewRemoved(child);
|
||||
@@ -124,21 +145,25 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
canvas.drawRect(0, canvas.getHeight() - mTaskbarView.getHeight(), canvas.getWidth(),
|
||||
canvas.getHeight(), mTaskbarBackgroundPaint);
|
||||
float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
|
||||
* (1f - mTaskbarBackgroundOffset);
|
||||
canvas.save();
|
||||
canvas.translate(0, canvas.getHeight() - backgroundHeight);
|
||||
|
||||
// Draw the background behind taskbar content.
|
||||
canvas.drawRect(0, 0, canvas.getWidth(), backgroundHeight, mTaskbarBackgroundPaint);
|
||||
|
||||
// Draw the inverted rounded corners above the taskbar.
|
||||
canvas.translate(0, -mLeftCornerRadius);
|
||||
canvas.drawPath(mInvertedLeftCornerPath, mTaskbarBackgroundPaint);
|
||||
canvas.translate(0, mLeftCornerRadius);
|
||||
canvas.translate(canvas.getWidth() - mRightCornerRadius, -mRightCornerRadius);
|
||||
canvas.drawPath(mInvertedRightCornerPath, mTaskbarBackgroundPaint);
|
||||
|
||||
canvas.restore();
|
||||
super.dispatchDraw(canvas);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Bounds (in our coordinates) where an opened Folder can display.
|
||||
*/
|
||||
protected Rect getFolderBoundingBox() {
|
||||
Rect boundingBox = new Rect(0, 0, getWidth(), getHeight() - mTaskbarView.getHeight());
|
||||
boundingBox.inset(mFolderMargin, mFolderMargin);
|
||||
return boundingBox;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the alpha of the background color behind all the Taskbar contents.
|
||||
* @param alpha 0 is fully transparent, 1 is fully opaque.
|
||||
@@ -147,4 +172,19 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
mTaskbarBackgroundPaint.setAlpha((int) (alpha * 255));
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the translation of the background color behind all the Taskbar contents.
|
||||
* @param offset 0 is fully onscreen, 1 is fully offscreen.
|
||||
*/
|
||||
protected void setTaskbarBackgroundOffset(float offset) {
|
||||
mTaskbarBackgroundOffset = offset;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||
TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
|
||||
return super.dispatchTouchEvent(ev);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
|
||||
import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_CONTENT;
|
||||
import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME;
|
||||
import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Rect;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AlphaUpdateListener;
|
||||
import com.android.quickstep.AnimatedFloat;
|
||||
import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
|
||||
|
||||
/**
|
||||
* Handles properties/data collection, then passes the results to TaskbarDragLayer to render.
|
||||
*/
|
||||
public class TaskbarDragLayerController {
|
||||
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final TaskbarDragLayer mTaskbarDragLayer;
|
||||
private final int mFolderMargin;
|
||||
|
||||
// Alpha properties for taskbar background.
|
||||
private final AnimatedFloat mBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
|
||||
private final AnimatedFloat mBgNavbar = new AnimatedFloat(this::updateBackgroundAlpha);
|
||||
private final AnimatedFloat mKeyguardBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
|
||||
private final AnimatedFloat mNotificationShadeBgTaskbar = new AnimatedFloat(
|
||||
this::updateBackgroundAlpha);
|
||||
private final AnimatedFloat mImeBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
|
||||
// Used to hide our background color when someone else (e.g. ScrimView) is handling it.
|
||||
private final AnimatedFloat mBgOverride = new AnimatedFloat(this::updateBackgroundAlpha);
|
||||
|
||||
// Translation property for taskbar background.
|
||||
private final AnimatedFloat mBgOffset = new AnimatedFloat(this::updateBackgroundOffset);
|
||||
|
||||
// Initialized in init.
|
||||
private TaskbarControllers mControllers;
|
||||
private AnimatedFloat mNavButtonDarkIntensityMultiplier;
|
||||
|
||||
private float mLastSetBackgroundAlpha;
|
||||
|
||||
public TaskbarDragLayerController(TaskbarActivityContext activity,
|
||||
TaskbarDragLayer taskbarDragLayer) {
|
||||
mActivity = activity;
|
||||
mTaskbarDragLayer = taskbarDragLayer;
|
||||
final Resources resources = mTaskbarDragLayer.getResources();
|
||||
mFolderMargin = resources.getDimensionPixelSize(R.dimen.taskbar_folder_margin);
|
||||
}
|
||||
|
||||
public void init(TaskbarControllers controllers) {
|
||||
mControllers = controllers;
|
||||
mTaskbarDragLayer.init(new TaskbarDragLayerCallbacks());
|
||||
|
||||
mNavButtonDarkIntensityMultiplier = mControllers.navbarButtonsViewController
|
||||
.getNavButtonDarkIntensityMultiplier();
|
||||
|
||||
mBgTaskbar.value = 1;
|
||||
mKeyguardBgTaskbar.value = 1;
|
||||
mNotificationShadeBgTaskbar.value = 1;
|
||||
mImeBgTaskbar.value = 1;
|
||||
mBgOverride.value = 1;
|
||||
updateBackgroundAlpha();
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
mTaskbarDragLayer.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Bounds (in TaskbarDragLayer coordinates) where an opened Folder can display.
|
||||
*/
|
||||
public Rect getFolderBoundingBox() {
|
||||
Rect boundingBox = new Rect(0, 0, mTaskbarDragLayer.getWidth(),
|
||||
mTaskbarDragLayer.getHeight() - mActivity.getDeviceProfile().taskbarSize);
|
||||
boundingBox.inset(mFolderMargin, mFolderMargin);
|
||||
return boundingBox;
|
||||
}
|
||||
|
||||
public AnimatedFloat getTaskbarBackgroundAlpha() {
|
||||
return mBgTaskbar;
|
||||
}
|
||||
|
||||
public AnimatedFloat getNavbarBackgroundAlpha() {
|
||||
return mBgNavbar;
|
||||
}
|
||||
|
||||
public AnimatedFloat getKeyguardBgTaskbar() {
|
||||
return mKeyguardBgTaskbar;
|
||||
}
|
||||
|
||||
public AnimatedFloat getNotificationShadeBgTaskbar() {
|
||||
return mNotificationShadeBgTaskbar;
|
||||
}
|
||||
|
||||
public AnimatedFloat getImeBgTaskbar() {
|
||||
return mImeBgTaskbar;
|
||||
}
|
||||
|
||||
public AnimatedFloat getOverrideBackgroundAlpha() {
|
||||
return mBgOverride;
|
||||
}
|
||||
|
||||
public AnimatedFloat getTaskbarBackgroundOffset() {
|
||||
return mBgOffset;
|
||||
}
|
||||
|
||||
private void updateBackgroundAlpha() {
|
||||
final float bgNavbar = mBgNavbar.value;
|
||||
final float bgTaskbar = mBgTaskbar.value * mKeyguardBgTaskbar.value
|
||||
* mNotificationShadeBgTaskbar.value * mImeBgTaskbar.value;
|
||||
mLastSetBackgroundAlpha = mBgOverride.value * Math.max(bgNavbar, bgTaskbar);
|
||||
mTaskbarDragLayer.setTaskbarBackgroundAlpha(mLastSetBackgroundAlpha);
|
||||
|
||||
updateNavBarDarkIntensityMultiplier();
|
||||
}
|
||||
|
||||
private void updateBackgroundOffset() {
|
||||
mTaskbarDragLayer.setTaskbarBackgroundOffset(mBgOffset.value);
|
||||
|
||||
updateNavBarDarkIntensityMultiplier();
|
||||
}
|
||||
|
||||
private void updateNavBarDarkIntensityMultiplier() {
|
||||
// Zero out the app-requested dark intensity when we're drawing our own background.
|
||||
float effectiveBgAlpha = mLastSetBackgroundAlpha * (1 - mBgOffset.value);
|
||||
mNavButtonDarkIntensityMultiplier.updateValue(1 - effectiveBgAlpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callbacks for {@link TaskbarDragLayer} to interact with its controller.
|
||||
*/
|
||||
public class TaskbarDragLayerCallbacks {
|
||||
|
||||
/**
|
||||
* Called to update the touchable insets.
|
||||
* @see InsetsInfo#setTouchableInsets(int)
|
||||
*/
|
||||
public void updateInsetsTouchability(InsetsInfo insetsInfo) {
|
||||
insetsInfo.touchableRegion.setEmpty();
|
||||
// Always have nav buttons be touchable
|
||||
mControllers.navbarButtonsViewController.addVisibleButtonsRegion(
|
||||
mTaskbarDragLayer, insetsInfo.touchableRegion);
|
||||
boolean insetsIsTouchableRegion = true;
|
||||
|
||||
if (mTaskbarDragLayer.getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
|
||||
// Let touches pass through us.
|
||||
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
|
||||
} else if (mControllers.navbarButtonsViewController.isImeVisible()) {
|
||||
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
|
||||
} else if (!mControllers.uiController.isTaskbarTouchable()) {
|
||||
// Let touches pass through us.
|
||||
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
|
||||
} else if (mControllers.taskbarViewController.areIconsVisible()
|
||||
|| AbstractFloatingView.getOpenView(mActivity, TYPE_ALL) != null) {
|
||||
// Taskbar has some touchable elements, take over the full taskbar area
|
||||
insetsInfo.setTouchableInsets(mActivity.isTaskbarWindowFullscreen()
|
||||
? TOUCHABLE_INSETS_FRAME : TOUCHABLE_INSETS_CONTENT);
|
||||
insetsIsTouchableRegion = false;
|
||||
} else {
|
||||
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
|
||||
}
|
||||
mActivity.excludeFromMagnificationRegion(insetsIsTouchableRegion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to update the {@link InsetsInfo#contentInsets}. This is reported to apps but our
|
||||
* internal launcher will ignore these insets.
|
||||
*/
|
||||
public void updateContentInsets(Rect outContentInsets) {
|
||||
int contentHeight = mControllers.taskbarStashController
|
||||
.getContentHeightToReportToApps();
|
||||
outContentInsets.top = mTaskbarDragLayer.getHeight() - contentHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a child is removed from TaskbarDragLayer.
|
||||
*/
|
||||
public void onDragLayerViewRemoved() {
|
||||
if (AbstractFloatingView.getAnyView(mActivity, TYPE_ALL) == null) {
|
||||
mActivity.setTaskbarWindowFullscreen(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how tall the background should be drawn at the bottom of the screen.
|
||||
*/
|
||||
public int getTaskbarBackgroundHeight() {
|
||||
return mActivity.getDeviceProfile().taskbarSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.dragndrop.DragView;
|
||||
|
||||
/**
|
||||
* A DragView drawn/used by the Taskbar. Note that this is only for the internal drag-and-drop,
|
||||
* while the pre-drag is still in progress (i.e. when the long press popup is still open). After
|
||||
* that ends, we switch to a system drag and drop view instead.
|
||||
*/
|
||||
public class TaskbarDragView extends DragView<TaskbarActivityContext> {
|
||||
public TaskbarDragView(TaskbarActivityContext launcher, Drawable drawable, int registrationX,
|
||||
int registrationY, float initialScale, float scaleOnDrop, float finalScaleDps) {
|
||||
super(launcher, drawable, registrationX, registrationY, initialScale, scaleOnDrop,
|
||||
finalScaleDps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration) {
|
||||
Runnable onAnimationEnd = () -> {
|
||||
if (onCompleteRunnable != null) {
|
||||
onCompleteRunnable.run();
|
||||
}
|
||||
mActivity.getDragLayer().removeView(this);
|
||||
};
|
||||
|
||||
duration = Math.max(duration,
|
||||
getResources().getInteger(R.integer.config_dropAnimMinDuration));
|
||||
|
||||
animate()
|
||||
.translationX(toTouchX - mRegistrationX)
|
||||
.translationY(toTouchY - mRegistrationY)
|
||||
.scaleX(mScaleOnDrop)
|
||||
.scaleY(mScaleOnDrop)
|
||||
.withEndAction(onAnimationEnd)
|
||||
.setDuration(duration)
|
||||
.start();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
|
||||
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.DEACCEL;
|
||||
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.Keyframe;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.content.res.Resources;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.uioverrides.PredictedAppIcon;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** Handles the Taskbar Education flow. */
|
||||
public class TaskbarEduController {
|
||||
|
||||
private static final long WAVE_ANIM_DELAY = 250;
|
||||
private static final long WAVE_ANIM_STAGGER = 50;
|
||||
private static final long WAVE_ANIM_EACH_ICON_DURATION = 633;
|
||||
private static final long WAVE_ANIM_SLOT_MACHINE_DURATION = 1085;
|
||||
// The fraction of each icon's animation at which we reach the top point of the wave.
|
||||
private static final float WAVE_ANIM_FRACTION_TOP = 0.4f;
|
||||
// The fraction of each icon's animation at which we reach the bottom, before overshooting.
|
||||
private static final float WAVE_ANIM_FRACTION_BOTTOM = 0.9f;
|
||||
private static final TimeInterpolator WAVE_ANIM_TO_TOP_INTERPOLATOR = FAST_OUT_SLOW_IN;
|
||||
private static final TimeInterpolator WAVE_ANIM_TO_BOTTOM_INTERPOLATOR = ACCEL_2;
|
||||
private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_INTERPOLATOR = DEACCEL;
|
||||
private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR = ACCEL_DEACCEL;
|
||||
private static final float WAVE_ANIM_ICON_SCALE = 1.2f;
|
||||
// How many icons to cycle through in the slot machine (+ the original icon at each end).
|
||||
private static final int WAVE_ANIM_SLOT_MACHINE_NUM_ICONS = 3;
|
||||
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final float mWaveAnimTranslationY;
|
||||
private final float mWaveAnimTranslationYReturnOvershoot;
|
||||
|
||||
// Initialized in init.
|
||||
TaskbarControllers mControllers;
|
||||
|
||||
private TaskbarEduView mTaskbarEduView;
|
||||
private Animator mAnim;
|
||||
|
||||
public TaskbarEduController(TaskbarActivityContext activity) {
|
||||
mActivity = activity;
|
||||
|
||||
final Resources resources = activity.getResources();
|
||||
mWaveAnimTranslationY = resources.getDimension(R.dimen.taskbar_edu_wave_anim_trans_y);
|
||||
mWaveAnimTranslationYReturnOvershoot = resources.getDimension(
|
||||
R.dimen.taskbar_edu_wave_anim_trans_y_return_overshoot);
|
||||
}
|
||||
|
||||
public void init(TaskbarControllers controllers) {
|
||||
mControllers = controllers;
|
||||
}
|
||||
|
||||
void showEdu() {
|
||||
mActivity.setTaskbarWindowFullscreen(true);
|
||||
mActivity.getDragLayer().post(() -> {
|
||||
mTaskbarEduView = (TaskbarEduView) mActivity.getLayoutInflater().inflate(
|
||||
R.layout.taskbar_edu, mActivity.getDragLayer(), false);
|
||||
mTaskbarEduView.init(new TaskbarEduCallbacks());
|
||||
mTaskbarEduView.addOnCloseListener(() -> mTaskbarEduView = null);
|
||||
mTaskbarEduView.show();
|
||||
startAnim(createWaveAnim());
|
||||
});
|
||||
}
|
||||
|
||||
void hideEdu() {
|
||||
if (mTaskbarEduView != null) {
|
||||
mTaskbarEduView.close(true /* animate */);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the given animation, ending the previous animation first if it's still playing.
|
||||
*/
|
||||
private void startAnim(Animator anim) {
|
||||
if (mAnim != null) {
|
||||
mAnim.end();
|
||||
}
|
||||
mAnim = anim;
|
||||
mAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mAnim = null;
|
||||
}
|
||||
});
|
||||
mAnim.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a staggered "wave" animation where each icon translates and scales up in succession.
|
||||
*/
|
||||
private Animator createWaveAnim() {
|
||||
AnimatorSet waveAnim = new AnimatorSet();
|
||||
View[] icons = mControllers.taskbarViewController.getIconViews();
|
||||
for (int i = 0; i < icons.length; i++) {
|
||||
View icon = icons[i];
|
||||
AnimatorSet iconAnim = new AnimatorSet();
|
||||
|
||||
Keyframe[] scaleKeyframes = new Keyframe[] {
|
||||
Keyframe.ofFloat(0, 1f),
|
||||
Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, WAVE_ANIM_ICON_SCALE),
|
||||
Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 1f),
|
||||
Keyframe.ofFloat(1f, 1f)
|
||||
};
|
||||
scaleKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR);
|
||||
scaleKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR);
|
||||
|
||||
Keyframe[] translationYKeyframes = new Keyframe[] {
|
||||
Keyframe.ofFloat(0, 0f),
|
||||
Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, -mWaveAnimTranslationY),
|
||||
Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 0f),
|
||||
// Half of the remaining fraction overshoots, then the other half returns to 0.
|
||||
Keyframe.ofFloat(
|
||||
WAVE_ANIM_FRACTION_BOTTOM + (1 - WAVE_ANIM_FRACTION_BOTTOM) / 2f,
|
||||
mWaveAnimTranslationYReturnOvershoot),
|
||||
Keyframe.ofFloat(1f, 0f)
|
||||
};
|
||||
translationYKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR);
|
||||
translationYKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR);
|
||||
translationYKeyframes[3].setInterpolator(WAVE_ANIM_OVERSHOOT_INTERPOLATOR);
|
||||
translationYKeyframes[4].setInterpolator(WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR);
|
||||
|
||||
iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon,
|
||||
PropertyValuesHolder.ofKeyframe(SCALE_PROPERTY, scaleKeyframes))
|
||||
.setDuration(WAVE_ANIM_EACH_ICON_DURATION));
|
||||
iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon,
|
||||
PropertyValuesHolder.ofKeyframe(View.TRANSLATION_Y, translationYKeyframes))
|
||||
.setDuration(WAVE_ANIM_EACH_ICON_DURATION));
|
||||
|
||||
if (icon instanceof PredictedAppIcon) {
|
||||
// Play slot machine animation through random icons from AllAppsList.
|
||||
PredictedAppIcon predictedAppIcon = (PredictedAppIcon) icon;
|
||||
ItemInfo itemInfo = (ItemInfo) icon.getTag();
|
||||
List<BitmapInfo> iconsToAnimate = mControllers.uiController.getAppIconsForEdu()
|
||||
.filter(appInfo -> !TextUtils.equals(appInfo.title, itemInfo.title))
|
||||
.map(appInfo -> appInfo.bitmap)
|
||||
.filter(bitmap -> !bitmap.isNullOrLowRes())
|
||||
.collect(Collectors.toList());
|
||||
// Pick n icons at random.
|
||||
Collections.shuffle(iconsToAnimate);
|
||||
if (iconsToAnimate.size() > WAVE_ANIM_SLOT_MACHINE_NUM_ICONS) {
|
||||
iconsToAnimate = iconsToAnimate.subList(0, WAVE_ANIM_SLOT_MACHINE_NUM_ICONS);
|
||||
}
|
||||
Animator slotMachineAnim = predictedAppIcon.createSlotMachineAnim(iconsToAnimate);
|
||||
if (slotMachineAnim != null) {
|
||||
iconAnim.play(slotMachineAnim.setDuration(WAVE_ANIM_SLOT_MACHINE_DURATION));
|
||||
}
|
||||
}
|
||||
|
||||
iconAnim.setStartDelay(WAVE_ANIM_STAGGER * i);
|
||||
waveAnim.play(iconAnim);
|
||||
}
|
||||
waveAnim.setStartDelay(WAVE_ANIM_DELAY);
|
||||
return waveAnim;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callbacks for {@link TaskbarEduView} to interact with its controller.
|
||||
*/
|
||||
class TaskbarEduCallbacks {
|
||||
void onPageChanged(int currentPage, int pageCount) {
|
||||
if (currentPage == 0) {
|
||||
mTaskbarEduView.updateStartButton(R.string.taskbar_edu_close,
|
||||
v -> mTaskbarEduView.close(true /* animate */));
|
||||
} else {
|
||||
mTaskbarEduView.updateStartButton(R.string.taskbar_edu_previous,
|
||||
v -> mTaskbarEduView.snapToPage(currentPage - 1));
|
||||
}
|
||||
if (currentPage == pageCount - 1) {
|
||||
mTaskbarEduView.updateEndButton(R.string.taskbar_edu_done,
|
||||
v -> mTaskbarEduView.close(true /* animate */));
|
||||
} else {
|
||||
mTaskbarEduView.updateEndButton(R.string.taskbar_edu_next,
|
||||
v -> mTaskbarEduView.snapToPage(currentPage + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_EDUCATION_DIALOG;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.PagedView;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.pageindicators.PageIndicatorDots;
|
||||
import com.android.launcher3.taskbar.TaskbarEduController.TaskbarEduCallbacks;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
|
||||
/** Horizontal carousel of tutorial screens for Taskbar Edu. */
|
||||
public class TaskbarEduPagedView extends PagedView<PageIndicatorDots> {
|
||||
|
||||
private TaskbarEduView mTaskbarEduView;
|
||||
private TaskbarEduCallbacks mControllerCallbacks;
|
||||
|
||||
public TaskbarEduPagedView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
|
||||
}
|
||||
|
||||
void setTaskbarEduView(TaskbarEduView taskbarEduView) {
|
||||
mTaskbarEduView = taskbarEduView;
|
||||
mPageIndicator = taskbarEduView.findViewById(R.id.content_page_indicator);
|
||||
initParentViews(taskbarEduView);
|
||||
}
|
||||
|
||||
void setControllerCallbacks(TaskbarEduCallbacks controllerCallbacks) {
|
||||
mControllerCallbacks = controllerCallbacks;
|
||||
mControllerCallbacks.onPageChanged(getCurrentPage(), getPageCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getChildGap() {
|
||||
return mTaskbarEduView.getPaddingLeft() + mTaskbarEduView.getPaddingRight();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
|
||||
super.onScrollChanged(l, t, oldl, oldt);
|
||||
if (mMaxScroll > 0) {
|
||||
mPageIndicator.setScroll(l, mMaxScroll);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notifyPageSwitchListener(int prevPage) {
|
||||
super.notifyPageSwitchListener(prevPage);
|
||||
mControllerCallbacks.onPageChanged(getCurrentPage(), getPageCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canScroll(float absVScroll, float absHScroll) {
|
||||
return AbstractFloatingView.getTopOpenViewWithType(
|
||||
ActivityContext.lookupContext(getContext()),
|
||||
TYPE_ALL & ~TYPE_TASKBAR_EDUCATION_DIALOG) == null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
|
||||
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.android.launcher3.Insettable;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.views.AbstractSlideInView;
|
||||
|
||||
/** Education view about the Taskbar. */
|
||||
public class TaskbarEduView extends AbstractSlideInView<TaskbarActivityContext>
|
||||
implements Insettable {
|
||||
|
||||
private static final int DEFAULT_OPEN_DURATION = 500;
|
||||
private static final int DEFAULT_CLOSE_DURATION = 200;
|
||||
|
||||
private final Rect mInsets = new Rect();
|
||||
|
||||
private Button mStartButton;
|
||||
private Button mEndButton;
|
||||
private TaskbarEduPagedView mPagedView;
|
||||
|
||||
public TaskbarEduView(Context context, AttributeSet attr) {
|
||||
this(context, attr, 0);
|
||||
}
|
||||
|
||||
public TaskbarEduView(Context context, AttributeSet attrs,
|
||||
int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
protected void init(TaskbarEduController.TaskbarEduCallbacks callbacks) {
|
||||
if (mPagedView != null) {
|
||||
mPagedView.setControllerCallbacks(callbacks);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleClose(boolean animate) {
|
||||
handleClose(animate, DEFAULT_CLOSE_DURATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isOfType(int type) {
|
||||
return (type & TYPE_TASKBAR_EDUCATION_DIALOG) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mContent = findViewById(R.id.edu_view);
|
||||
mStartButton = findViewById(R.id.edu_start_button);
|
||||
mEndButton = findViewById(R.id.edu_end_button);
|
||||
mPagedView = findViewById(R.id.content);
|
||||
mPagedView.setTaskbarEduView(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInsets(Rect insets) {
|
||||
mInsets.set(insets);
|
||||
mContent.setPadding(mContent.getPaddingStart(),
|
||||
mContent.getPaddingTop(), mContent.getPaddingEnd(), insets.bottom);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachToContainer() {
|
||||
if (mColorScrim != null) {
|
||||
getPopupContainer().addView(mColorScrim, 0);
|
||||
}
|
||||
getPopupContainer().addView(this, 1);
|
||||
}
|
||||
|
||||
/** Show the Education flow. */
|
||||
public void show() {
|
||||
attachToContainer();
|
||||
animateOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pair<View, String> getAccessibilityTarget() {
|
||||
return Pair.create(mContent, mIsOpen ? getContext().getString(R.string.taskbar_edu_opened)
|
||||
: getContext().getString(R.string.taskbar_edu_closed));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getScrimColor(Context context) {
|
||||
return context.getResources().getColor(R.color.widgets_picker_scrim);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
int width = r - l;
|
||||
int height = b - t;
|
||||
|
||||
// Lay out the content as center bottom aligned.
|
||||
int contentWidth = mContent.getMeasuredWidth();
|
||||
int contentLeft = (width - contentWidth - mInsets.left - mInsets.right) / 2 + mInsets.left;
|
||||
mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
|
||||
contentLeft + contentWidth, height);
|
||||
|
||||
setTranslationShift(mTranslationShift);
|
||||
}
|
||||
|
||||
private void animateOpen() {
|
||||
if (mIsOpen || mOpenCloseAnimator.isRunning()) {
|
||||
return;
|
||||
}
|
||||
mIsOpen = true;
|
||||
mOpenCloseAnimator.setValues(
|
||||
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
|
||||
mOpenCloseAnimator.setInterpolator(AGGRESSIVE_EASE);
|
||||
mOpenCloseAnimator.setDuration(DEFAULT_OPEN_DURATION).start();
|
||||
}
|
||||
|
||||
void snapToPage(int page) {
|
||||
mPagedView.snapToPage(page);
|
||||
}
|
||||
|
||||
void updateStartButton(int textResId, OnClickListener onClickListener) {
|
||||
mStartButton.setText(textResId);
|
||||
mStartButton.setOnClickListener(onClickListener);
|
||||
}
|
||||
|
||||
void updateEndButton(int textResId, OnClickListener onClickListener) {
|
||||
mEndButton.setText(textResId);
|
||||
mEndButton.setOnClickListener(onClickListener);
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.BaseQuickstepLauncher;
|
||||
import com.android.launcher3.CellLayout;
|
||||
import com.android.launcher3.DropTarget;
|
||||
import com.android.launcher3.Hotseat;
|
||||
import com.android.launcher3.ShortcutAndWidgetContainer;
|
||||
import com.android.launcher3.dragndrop.DragController;
|
||||
import com.android.launcher3.dragndrop.DragOptions;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Works with TaskbarController to update the TaskbarView's Hotseat items.
|
||||
*/
|
||||
public class TaskbarHotseatController {
|
||||
|
||||
private final BaseQuickstepLauncher mLauncher;
|
||||
private final Hotseat mHotseat;
|
||||
private final Consumer<ItemInfo[]> mTaskbarCallbacks;
|
||||
private final int mNumHotseatIcons;
|
||||
|
||||
private final DragController.DragListener mDragListener = new DragController.DragListener() {
|
||||
@Override
|
||||
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { }
|
||||
|
||||
@Override
|
||||
public void onDragEnd() {
|
||||
onHotseatUpdated();
|
||||
}
|
||||
};
|
||||
|
||||
public TaskbarHotseatController(
|
||||
BaseQuickstepLauncher launcher, Consumer<ItemInfo[]> taskbarCallbacks) {
|
||||
mLauncher = launcher;
|
||||
mHotseat = mLauncher.getHotseat();
|
||||
mTaskbarCallbacks = taskbarCallbacks;
|
||||
mNumHotseatIcons = mLauncher.getDeviceProfile().numShownHotseatIcons;
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
mLauncher.getDragController().addDragListener(mDragListener);
|
||||
onHotseatUpdated();
|
||||
}
|
||||
|
||||
protected void cleanup() {
|
||||
mLauncher.getDragController().removeDragListener(mDragListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when any Hotseat item changes, and reports the new list of items to TaskbarController.
|
||||
*/
|
||||
protected void onHotseatUpdated() {
|
||||
ShortcutAndWidgetContainer shortcutsAndWidgets = mHotseat.getShortcutsAndWidgets();
|
||||
ItemInfo[] hotseatItemInfos = new ItemInfo[mNumHotseatIcons];
|
||||
for (int i = 0; i < shortcutsAndWidgets.getChildCount(); i++) {
|
||||
View child = shortcutsAndWidgets.getChildAt(i);
|
||||
Object tag = shortcutsAndWidgets.getChildAt(i).getTag();
|
||||
if (tag instanceof ItemInfo) {
|
||||
ItemInfo itemInfo = (ItemInfo) tag;
|
||||
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
|
||||
// Since the hotseat might be laid out vertically or horizontally, use whichever
|
||||
// index is higher.
|
||||
int index = Math.max(lp.cellX, lp.cellY);
|
||||
if (0 <= index && index < hotseatItemInfos.length) {
|
||||
hotseatItemInfos[index] = itemInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mTaskbarCallbacks.accept(hotseatItemInfos);
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME;
|
||||
import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnLongClickListener;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AlphaUpdateListener;
|
||||
import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
|
||||
|
||||
/**
|
||||
* Controller for taskbar icon UI
|
||||
*/
|
||||
public class TaskbarIconController {
|
||||
|
||||
private final Rect mTempRect = new Rect();
|
||||
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final TaskbarDragLayer mDragLayer;
|
||||
|
||||
private final TaskbarView mTaskbarView;
|
||||
private final ImeBarView mImeBarView;
|
||||
|
||||
@NonNull
|
||||
private TaskbarUIController mUIController = TaskbarUIController.DEFAULT;
|
||||
|
||||
TaskbarIconController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
|
||||
mActivity = activity;
|
||||
mDragLayer = dragLayer;
|
||||
mTaskbarView = mDragLayer.findViewById(R.id.taskbar_view);
|
||||
mImeBarView = mDragLayer.findViewById(R.id.ime_bar_view);
|
||||
}
|
||||
|
||||
public void init(OnClickListener clickListener, OnLongClickListener longClickListener) {
|
||||
mDragLayer.addOnLayoutChangeListener((v, a, b, c, d, e, f, g, h) ->
|
||||
mUIController.alignRealHotseatWithTaskbar());
|
||||
|
||||
ButtonProvider buttonProvider = new ButtonProvider(mActivity);
|
||||
mImeBarView.init(buttonProvider);
|
||||
mTaskbarView.construct(clickListener, longClickListener, buttonProvider);
|
||||
mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
|
||||
|
||||
mDragLayer.init(new Callbacks(), mTaskbarView);
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
mDragLayer.onDestroy();
|
||||
}
|
||||
|
||||
public void setUIController(@NonNull TaskbarUIController uiController) {
|
||||
mUIController = uiController;
|
||||
}
|
||||
|
||||
/**
|
||||
* When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from
|
||||
* instantiating at all, which is what's responsible for sending sysui state flags over.
|
||||
*
|
||||
* @param vis IME visibility flag
|
||||
*/
|
||||
public void updateImeStatus(int displayId, int vis, boolean showImeSwitcher) {
|
||||
if (displayId != mActivity.getDisplayId() || !mActivity.canShowNavButtons()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mImeBarView.setImeSwitcherVisibility(showImeSwitcher);
|
||||
setImeIsVisible((vis & InputMethodService.IME_VISIBLE) != 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly.
|
||||
*/
|
||||
public void setImeIsVisible(boolean isImeVisible) {
|
||||
mTaskbarView.setTouchesEnabled(!isImeVisible);
|
||||
mUIController.onImeVisible(mDragLayer, isImeVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callbacks for {@link TaskbarDragLayer} to interact with the icon controller
|
||||
*/
|
||||
public class Callbacks {
|
||||
|
||||
/**
|
||||
* Called to update the touchable insets
|
||||
*/
|
||||
public void updateInsetsTouchability(InsetsInfo insetsInfo) {
|
||||
insetsInfo.touchableRegion.setEmpty();
|
||||
if (mDragLayer.getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
|
||||
// Let touches pass through us.
|
||||
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
|
||||
} else if (mImeBarView.getVisibility() == VISIBLE) {
|
||||
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
|
||||
} else if (!mUIController.isTaskbarTouchable()) {
|
||||
// Let touches pass through us.
|
||||
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
|
||||
} else if (mTaskbarView.areIconsVisible()) {
|
||||
// Buttons are visible, take over the full taskbar area
|
||||
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
|
||||
} else {
|
||||
if (mTaskbarView.mSystemButtonContainer.getVisibility() == VISIBLE) {
|
||||
mDragLayer.getDescendantRectRelativeToSelf(
|
||||
mTaskbarView.mSystemButtonContainer, mTempRect);
|
||||
insetsInfo.touchableRegion.set(mTempRect);
|
||||
}
|
||||
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
|
||||
}
|
||||
|
||||
// TaskbarContainerView provides insets to other apps based on contentInsets. These
|
||||
// insets should stay consistent even if we expand TaskbarContainerView's bounds, e.g.
|
||||
// to show a floating view like Folder. Thus, we set the contentInsets to be where
|
||||
// mTaskbarView is, since its position never changes and insets rather than overlays.
|
||||
insetsInfo.contentInsets.left = mTaskbarView.getLeft();
|
||||
insetsInfo.contentInsets.top = mTaskbarView.getTop();
|
||||
insetsInfo.contentInsets.right = mDragLayer.getWidth() - mTaskbarView.getRight();
|
||||
insetsInfo.contentInsets.bottom = mDragLayer.getHeight() - mTaskbarView.getBottom();
|
||||
}
|
||||
|
||||
public void onDragLayerViewRemoved() {
|
||||
int count = mDragLayer.getChildCount();
|
||||
// Ensure no other children present (like Folders, etc)
|
||||
for (int i = 0; i < count; i++) {
|
||||
View v = mDragLayer.getChildAt(i);
|
||||
if (!((v instanceof TaskbarView) || (v instanceof ImeBarView))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
mActivity.setTaskbarWindowFullscreen(false);
|
||||
}
|
||||
|
||||
public void updateImeBarVisibilityAlpha(float alpha) {
|
||||
if (!mActivity.canShowNavButtons()) {
|
||||
// TODO Remove sysui IME bar for gesture nav as well
|
||||
return;
|
||||
}
|
||||
mImeBarView.setAlpha(alpha);
|
||||
mImeBarView.setVisibility(alpha == 0 ? GONE : VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
|
||||
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
|
||||
/**
|
||||
* Controller for managing keyguard state for taskbar
|
||||
*/
|
||||
public class TaskbarKeyguardController {
|
||||
|
||||
private static final int KEYGUARD_SYSUI_FLAGS = SYSUI_STATE_BOUNCER_SHOWING |
|
||||
SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING | SYSUI_STATE_DEVICE_DOZING |
|
||||
SYSUI_STATE_OVERVIEW_DISABLED | SYSUI_STATE_HOME_DISABLED |
|
||||
SYSUI_STATE_BACK_DISABLED | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
|
||||
|
||||
private final TaskbarActivityContext mContext;
|
||||
private int mKeyguardSysuiFlags;
|
||||
private boolean mBouncerShowing;
|
||||
private NavbarButtonsViewController mNavbarButtonsViewController;
|
||||
private final KeyguardManager mKeyguardManager;
|
||||
private boolean mIsScreenOff;
|
||||
|
||||
private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
mIsScreenOff = true;
|
||||
}
|
||||
};
|
||||
|
||||
public TaskbarKeyguardController(TaskbarActivityContext context) {
|
||||
mContext = context;
|
||||
mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
|
||||
}
|
||||
|
||||
public void init(NavbarButtonsViewController navbarButtonUIController) {
|
||||
mNavbarButtonsViewController = navbarButtonUIController;
|
||||
mContext.registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
|
||||
}
|
||||
|
||||
public void updateStateForSysuiFlags(int systemUiStateFlags) {
|
||||
boolean bouncerShowing = (systemUiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0;
|
||||
boolean keyguardShowing = (systemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING)
|
||||
!= 0;
|
||||
boolean keyguardOccluded =
|
||||
(systemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0;
|
||||
boolean dozing = (systemUiStateFlags & SYSUI_STATE_DEVICE_DOZING) != 0;
|
||||
|
||||
int interestingKeyguardFlags = systemUiStateFlags & KEYGUARD_SYSUI_FLAGS;
|
||||
if (interestingKeyguardFlags == mKeyguardSysuiFlags) {
|
||||
return;
|
||||
}
|
||||
mKeyguardSysuiFlags = interestingKeyguardFlags;
|
||||
|
||||
mBouncerShowing = bouncerShowing;
|
||||
|
||||
mNavbarButtonsViewController.setKeyguardVisible(keyguardShowing || dozing,
|
||||
keyguardOccluded);
|
||||
updateIconsForBouncer();
|
||||
}
|
||||
|
||||
public boolean isScreenOff() {
|
||||
return mIsScreenOff;
|
||||
}
|
||||
|
||||
public void setScreenOn() {
|
||||
mIsScreenOff = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides/shows taskbar when keyguard is up
|
||||
*/
|
||||
private void updateIconsForBouncer() {
|
||||
boolean disableBack = (mKeyguardSysuiFlags & SYSUI_STATE_BACK_DISABLED) != 0;
|
||||
boolean disableRecent = (mKeyguardSysuiFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
|
||||
boolean disableHome = (mKeyguardSysuiFlags & SYSUI_STATE_HOME_DISABLED) != 0;
|
||||
boolean onlyBackEnabled = !disableBack && disableRecent && disableHome;
|
||||
|
||||
boolean showBackForBouncer = onlyBackEnabled &&
|
||||
mKeyguardManager.isDeviceSecure() &&
|
||||
mBouncerShowing;
|
||||
mNavbarButtonsViewController.setBackForBouncer(showBackForBouncer);
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
mContext.unregisterReceiver(mScreenOffReceiver);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
|
||||
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
|
||||
import static com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION;
|
||||
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.BaseQuickstepLauncher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.statemanager.StateManager;
|
||||
import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.quickstep.AnimatedFloat;
|
||||
import com.android.quickstep.RecentsAnimationCallbacks;
|
||||
import com.android.quickstep.RecentsAnimationController;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Track LauncherState, RecentsAnimation, resumed state for task bar in one place here and animate
|
||||
* the task bar accordingly.
|
||||
*/
|
||||
public class TaskbarLauncherStateController {
|
||||
|
||||
public static final int FLAG_RESUMED = 1 << 0;
|
||||
public static final int FLAG_RECENTS_ANIMATION_RUNNING = 1 << 1;
|
||||
public static final int FLAG_TRANSITION_STATE_RUNNING = 1 << 2;
|
||||
|
||||
/** Equivalent to an int with all 1s for binary operation purposes */
|
||||
private static final int FLAGS_ALL = ~0;
|
||||
|
||||
private final AnimatedFloat mIconAlignmentForResumedState =
|
||||
new AnimatedFloat(this::onIconAlignmentRatioChangedForAppAndHomeTransition);
|
||||
private final AnimatedFloat mIconAlignmentForGestureState =
|
||||
new AnimatedFloat(this::onIconAlignmentRatioChangedForAppAndHomeTransition);
|
||||
private final AnimatedFloat mIconAlignmentForLauncherState =
|
||||
new AnimatedFloat(this::onIconAlignmentRatioChangedForStateTransition);
|
||||
|
||||
private TaskbarControllers mControllers;
|
||||
private AnimatedFloat mTaskbarBackgroundAlpha;
|
||||
private MultiValueAlpha.AlphaProperty mIconAlphaForHome;
|
||||
private BaseQuickstepLauncher mLauncher;
|
||||
|
||||
private Integer mPrevState;
|
||||
private int mState;
|
||||
private LauncherState mLauncherState = LauncherState.NORMAL;
|
||||
|
||||
private boolean mIsAnimatingToLauncherViaGesture;
|
||||
private boolean mIsAnimatingToLauncherViaResume;
|
||||
|
||||
private final StateManager.StateListener<LauncherState> mStateListener =
|
||||
new StateManager.StateListener<LauncherState>() {
|
||||
|
||||
@Override
|
||||
public void onStateTransitionStart(LauncherState toState) {
|
||||
if (toState != mLauncherState) {
|
||||
// Treat FLAG_TRANSITION_STATE_RUNNING as a changed flag even if a previous
|
||||
// state transition was already running, so we update the new target.
|
||||
mPrevState &= ~FLAG_TRANSITION_STATE_RUNNING;
|
||||
mLauncherState = toState;
|
||||
}
|
||||
updateStateForFlag(FLAG_TRANSITION_STATE_RUNNING, true);
|
||||
applyState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateTransitionComplete(LauncherState finalState) {
|
||||
mLauncherState = finalState;
|
||||
updateStateForFlag(FLAG_TRANSITION_STATE_RUNNING, false);
|
||||
applyState();
|
||||
}
|
||||
};
|
||||
|
||||
public void init(TaskbarControllers controllers, BaseQuickstepLauncher launcher) {
|
||||
mControllers = controllers;
|
||||
mLauncher = launcher;
|
||||
|
||||
mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController
|
||||
.getTaskbarBackgroundAlpha();
|
||||
MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha();
|
||||
mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME);
|
||||
mIconAlphaForHome.setConsumer(
|
||||
(Consumer<Float>) alpha -> mLauncher.getHotseat().setIconsAlpha(alpha > 0 ? 0 : 1));
|
||||
|
||||
mIconAlignmentForResumedState.finishAnimation();
|
||||
onIconAlignmentRatioChangedForAppAndHomeTransition();
|
||||
|
||||
mLauncher.getStateManager().addStateListener(mStateListener);
|
||||
|
||||
// Initialize to the current launcher state
|
||||
updateStateForFlag(FLAG_RESUMED, launcher.hasBeenResumed());
|
||||
mLauncherState = launcher.getStateManager().getState();
|
||||
applyState(0);
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
mIconAlignmentForResumedState.finishAnimation();
|
||||
mIconAlignmentForGestureState.finishAnimation();
|
||||
mIconAlignmentForLauncherState.finishAnimation();
|
||||
|
||||
mIconAlphaForHome.setConsumer(null);
|
||||
mLauncher.getHotseat().setIconsAlpha(1f);
|
||||
mLauncher.getStateManager().removeStateListener(mStateListener);
|
||||
}
|
||||
|
||||
public Animator createAnimToLauncher(@NonNull LauncherState toState,
|
||||
@NonNull RecentsAnimationCallbacks callbacks, long duration) {
|
||||
// If going to overview, stash the task bar
|
||||
// If going home, align the icons to hotseat
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
|
||||
// Update stashed flags first to ensure goingToUnstashedLauncherState() returns correctly.
|
||||
TaskbarStashController stashController = mControllers.taskbarStashController;
|
||||
stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
|
||||
toState.isTaskbarStashed(mLauncher));
|
||||
stashController.updateStateForFlag(FLAG_IN_APP, false);
|
||||
|
||||
updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, true);
|
||||
animatorSet.play(stashController.applyStateWithoutStart(duration));
|
||||
animatorSet.play(applyState(duration, false));
|
||||
|
||||
TaskBarRecentsAnimationListener listener = new TaskBarRecentsAnimationListener(callbacks);
|
||||
callbacks.addListener(listener);
|
||||
RecentsView recentsView = mLauncher.getOverviewPanel();
|
||||
recentsView.setTaskLaunchListener(() -> {
|
||||
listener.endGestureStateOverride(true);
|
||||
callbacks.removeListener(listener);
|
||||
});
|
||||
return animatorSet;
|
||||
}
|
||||
|
||||
public boolean isAnimatingToLauncher() {
|
||||
return mIsAnimatingToLauncherViaResume || mIsAnimatingToLauncherViaGesture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the proper flag to change the state of the task bar.
|
||||
*
|
||||
* Note that this only updates the flag. {@link #applyState()} needs to be called separately.
|
||||
*
|
||||
* @param flag The flag to update.
|
||||
* @param enabled Whether to enable the flag
|
||||
*/
|
||||
public void updateStateForFlag(int flag, boolean enabled) {
|
||||
if (enabled) {
|
||||
mState |= flag;
|
||||
} else {
|
||||
mState &= ~flag;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasAnyFlag(int flagMask) {
|
||||
return hasAnyFlag(mState, flagMask);
|
||||
}
|
||||
|
||||
private boolean hasAnyFlag(int flags, int flagMask) {
|
||||
return (flags & flagMask) != 0;
|
||||
}
|
||||
|
||||
public void applyState() {
|
||||
applyState(TASKBAR_STASH_DURATION);
|
||||
}
|
||||
|
||||
public void applyState(long duration) {
|
||||
applyState(duration, true);
|
||||
}
|
||||
|
||||
public Animator applyState(boolean start) {
|
||||
return applyState(TASKBAR_STASH_DURATION, start);
|
||||
}
|
||||
|
||||
public Animator applyState(long duration, boolean start) {
|
||||
Animator animator = null;
|
||||
if (mPrevState == null || mPrevState != mState) {
|
||||
// If this is our initial state, treat all flags as changed.
|
||||
int changedFlags = mPrevState == null ? FLAGS_ALL : mPrevState ^ mState;
|
||||
mPrevState = mState;
|
||||
animator = onStateChangeApplied(changedFlags, duration, start);
|
||||
}
|
||||
return animator;
|
||||
}
|
||||
|
||||
private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) {
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
if (hasAnyFlag(changedFlags, FLAG_RESUMED)) {
|
||||
boolean isResumed = isResumed();
|
||||
ObjectAnimator anim = mIconAlignmentForResumedState
|
||||
.animateToValue(isResumed && goingToUnstashedLauncherState()
|
||||
? 1 : 0)
|
||||
.setDuration(duration);
|
||||
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mIsAnimatingToLauncherViaResume = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mIsAnimatingToLauncherViaResume = isResumed;
|
||||
|
||||
TaskbarStashController stashController = mControllers.taskbarStashController;
|
||||
stashController.updateStateForFlag(FLAG_IN_APP, !isResumed);
|
||||
stashController.applyState(duration);
|
||||
}
|
||||
});
|
||||
animatorSet.play(anim);
|
||||
}
|
||||
|
||||
if (hasAnyFlag(changedFlags, FLAG_RECENTS_ANIMATION_RUNNING)) {
|
||||
boolean isRecentsAnimationRunning = isRecentsAnimationRunning();
|
||||
Animator animator = mIconAlignmentForGestureState
|
||||
.animateToValue(isRecentsAnimationRunning && goingToUnstashedLauncherState()
|
||||
? 1 : 0);
|
||||
if (isRecentsAnimationRunning) {
|
||||
animator.setDuration(duration);
|
||||
}
|
||||
animator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mIsAnimatingToLauncherViaGesture = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mIsAnimatingToLauncherViaGesture = isRecentsAnimationRunning();
|
||||
}
|
||||
});
|
||||
animatorSet.play(animator);
|
||||
}
|
||||
|
||||
if (hasAnyFlag(changedFlags, FLAG_RESUMED | FLAG_RECENTS_ANIMATION_RUNNING)) {
|
||||
boolean goingToLauncher = hasAnyFlag(FLAG_RESUMED | FLAG_RECENTS_ANIMATION_RUNNING);
|
||||
animatorSet.play(mTaskbarBackgroundAlpha.animateToValue(goingToLauncher ? 0 : 1)
|
||||
.setDuration(duration));
|
||||
}
|
||||
|
||||
if (hasAnyFlag(changedFlags, FLAG_TRANSITION_STATE_RUNNING)) {
|
||||
boolean committed = !hasAnyFlag(FLAG_TRANSITION_STATE_RUNNING);
|
||||
playStateTransitionAnim(animatorSet, duration, committed);
|
||||
|
||||
if (committed && mLauncherState == LauncherState.QUICK_SWITCH) {
|
||||
// We're about to be paused, set immediately to ensure seamless handoff.
|
||||
updateStateForFlag(FLAG_RESUMED, false);
|
||||
applyState(0 /* duration */);
|
||||
}
|
||||
}
|
||||
|
||||
if (start) {
|
||||
animatorSet.start();
|
||||
}
|
||||
return animatorSet;
|
||||
}
|
||||
|
||||
/** Returns whether we're going to a state where taskbar icons should align with launcher. */
|
||||
private boolean goingToUnstashedLauncherState() {
|
||||
return !mControllers.taskbarStashController.isInStashedLauncherState();
|
||||
}
|
||||
|
||||
private void playStateTransitionAnim(AnimatorSet animatorSet, long duration,
|
||||
boolean committed) {
|
||||
boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
|
||||
float toAlignment = mLauncherState.isTaskbarAlignedWithHotseat(mLauncher) ? 1 : 0;
|
||||
|
||||
TaskbarStashController controller = mControllers.taskbarStashController;
|
||||
controller.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState);
|
||||
Animator stashAnimator = controller.applyStateWithoutStart(duration);
|
||||
if (stashAnimator != null) {
|
||||
stashAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (isInStashedState && committed) {
|
||||
// Reset hotseat alpha to default
|
||||
mLauncher.getHotseat().setIconsAlpha(1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
if (mLauncher.getHotseat().getIconsAlpha() > 0) {
|
||||
mIconAlphaForHome.setValue(mLauncher.getHotseat().getIconsAlpha());
|
||||
}
|
||||
}
|
||||
});
|
||||
animatorSet.play(stashAnimator);
|
||||
}
|
||||
|
||||
animatorSet.play(mIconAlignmentForLauncherState.animateToValue(toAlignment)
|
||||
.setDuration(duration));
|
||||
}
|
||||
|
||||
private boolean isResumed() {
|
||||
return (mState & FLAG_RESUMED) != 0;
|
||||
}
|
||||
|
||||
private boolean isRecentsAnimationRunning() {
|
||||
return (mState & FLAG_RECENTS_ANIMATION_RUNNING) != 0;
|
||||
}
|
||||
|
||||
private void onIconAlignmentRatioChangedForStateTransition() {
|
||||
if (!isResumed()) {
|
||||
return;
|
||||
}
|
||||
onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatioForLauncherState);
|
||||
}
|
||||
|
||||
private void onIconAlignmentRatioChangedForAppAndHomeTransition() {
|
||||
onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatioBetweenAppAndHome);
|
||||
}
|
||||
|
||||
private void onIconAlignmentRatioChanged(Supplier<Float> alignmentSupplier) {
|
||||
if (mControllers == null) {
|
||||
return;
|
||||
}
|
||||
float alignment = alignmentSupplier.get();
|
||||
mControllers.taskbarViewController.setLauncherIconAlignment(
|
||||
alignment, mLauncher.getDeviceProfile());
|
||||
|
||||
// Switch taskbar and hotseat in last frame
|
||||
setTaskbarViewVisible(alignment < 1);
|
||||
}
|
||||
|
||||
private float getCurrentIconAlignmentRatioBetweenAppAndHome() {
|
||||
return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value);
|
||||
}
|
||||
|
||||
private float getCurrentIconAlignmentRatioForLauncherState() {
|
||||
return mIconAlignmentForLauncherState.value;
|
||||
}
|
||||
|
||||
private void setTaskbarViewVisible(boolean isVisible) {
|
||||
mIconAlphaForHome.setValue(isVisible ? 1 : 0);
|
||||
}
|
||||
|
||||
private final class TaskBarRecentsAnimationListener implements
|
||||
RecentsAnimationCallbacks.RecentsAnimationListener {
|
||||
private final RecentsAnimationCallbacks mCallbacks;
|
||||
|
||||
TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks) {
|
||||
mCallbacks = callbacks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
|
||||
endGestureStateOverride(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
|
||||
endGestureStateOverride(!controller.getFinishTargetIsLauncher());
|
||||
}
|
||||
|
||||
private void endGestureStateOverride(boolean finishedToApp) {
|
||||
mCallbacks.removeListener(this);
|
||||
|
||||
// Update the resumed state immediately to ensure a seamless handoff
|
||||
boolean launcherResumed = !finishedToApp;
|
||||
updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, false);
|
||||
updateStateForFlag(FLAG_RESUMED, launcherResumed);
|
||||
applyState();
|
||||
// Set this last because applyState() might also animate it.
|
||||
mIconAlignmentForResumedState.cancelAnimation();
|
||||
mIconAlignmentForResumedState.updateValue(launcherResumed ? 1 : 0);
|
||||
|
||||
TaskbarStashController controller = mControllers.taskbarStashController;
|
||||
controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
|
||||
controller.applyState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,18 +16,24 @@
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
|
||||
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
|
||||
|
||||
import android.content.ComponentCallbacks;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.provider.Settings;
|
||||
import android.view.Display;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.BaseQuickstepLauncher;
|
||||
@@ -35,25 +41,47 @@ import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.statemanager.StatefulActivity;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.DisplayController.Info;
|
||||
import com.android.launcher3.util.SettingsCache;
|
||||
import com.android.launcher3.util.SimpleBroadcastReceiver;
|
||||
import com.android.quickstep.RecentsActivity;
|
||||
import com.android.quickstep.SysUINavigationMode;
|
||||
import com.android.quickstep.SysUINavigationMode.Mode;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.TouchInteractionService;
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
|
||||
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
|
||||
|
||||
/**
|
||||
* Class to manager taskbar lifecycle
|
||||
* Class to manage taskbar lifecycle
|
||||
*/
|
||||
public class TaskbarManager implements DisplayController.DisplayInfoChangeListener,
|
||||
SysUINavigationMode.NavigationModeChangeListener {
|
||||
|
||||
private static final Uri USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(
|
||||
Settings.Secure.USER_SETUP_COMPLETE);
|
||||
|
||||
private final Context mContext;
|
||||
private final DisplayController mDisplayController;
|
||||
private final SysUINavigationMode mSysUINavigationMode;
|
||||
private final TaskbarNavButtonController mNavButtonController;
|
||||
private final SettingsCache.OnChangeListener mUserSetupCompleteListener;
|
||||
private final ComponentCallbacks mComponentCallbacks;
|
||||
private final SimpleBroadcastReceiver mShutdownReceiver;
|
||||
|
||||
// The source for this provider is set when Launcher is available
|
||||
private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider =
|
||||
new ScopedUnfoldTransitionProgressProvider();
|
||||
|
||||
private TaskbarActivityContext mTaskbarActivityContext;
|
||||
private BaseQuickstepLauncher mLauncher;
|
||||
private StatefulActivity mActivity;
|
||||
/**
|
||||
* Cache a copy here so we can initialize state whenever taskbar is recreated, since
|
||||
* this class does not get re-initialized w/ new taskbars.
|
||||
*/
|
||||
private final TaskbarSharedState mSharedState = new TaskbarSharedState();
|
||||
|
||||
private static final int CHANGE_FLAGS =
|
||||
CHANGE_ACTIVE_SCREEN | CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS;
|
||||
@@ -66,12 +94,43 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen
|
||||
Display display =
|
||||
service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
|
||||
mContext = Utilities.ATLEAST_S
|
||||
? service.createWindowContext(display, TYPE_APPLICATION_OVERLAY, null)
|
||||
? service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
|
||||
: null;
|
||||
mNavButtonController = new TaskbarNavButtonController(service);
|
||||
mNavButtonController = new TaskbarNavButtonController(service,
|
||||
SystemUiProxy.INSTANCE.get(mContext), new Handler());
|
||||
mUserSetupCompleteListener = isUserSetupComplete -> recreateTaskbar();
|
||||
mComponentCallbacks = new ComponentCallbacks() {
|
||||
private Configuration mOldConfig = mContext.getResources().getConfiguration();
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
int configDiff = mOldConfig.diff(newConfig);
|
||||
int configsRequiringRecreate = ActivityInfo.CONFIG_ASSETS_PATHS
|
||||
| ActivityInfo.CONFIG_LAYOUT_DIRECTION | ActivityInfo.CONFIG_UI_MODE;
|
||||
if ((configDiff & configsRequiringRecreate) != 0) {
|
||||
// Color has changed, recreate taskbar to reload background color & icons.
|
||||
recreateTaskbar();
|
||||
} else {
|
||||
// Config change might be handled without re-creating the taskbar
|
||||
if (mTaskbarActivityContext != null) {
|
||||
mTaskbarActivityContext.onConfigurationChanged(configDiff);
|
||||
}
|
||||
}
|
||||
mOldConfig = newConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLowMemory() { }
|
||||
};
|
||||
mShutdownReceiver = new SimpleBroadcastReceiver(i -> destroyExistingTaskbar());
|
||||
|
||||
mDisplayController.addChangeListener(this);
|
||||
mSysUINavigationMode.addModeChangeListener(this);
|
||||
SettingsCache.INSTANCE.get(mContext).register(USER_SETUP_COMPLETE_URI,
|
||||
mUserSetupCompleteListener);
|
||||
mContext.registerComponentCallbacks(mComponentCallbacks);
|
||||
mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN);
|
||||
|
||||
recreateTaskbar();
|
||||
}
|
||||
|
||||
@@ -103,61 +162,120 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or clears a launcher to act as taskbar callback
|
||||
* Sets a {@link StatefulActivity} to act as taskbar callback
|
||||
*/
|
||||
public void setLauncher(@Nullable BaseQuickstepLauncher launcher) {
|
||||
mLauncher = launcher;
|
||||
public void setActivity(@NonNull StatefulActivity activity) {
|
||||
mActivity = activity;
|
||||
mUnfoldProgressProvider.setSourceProvider(getUnfoldTransitionProgressProviderForActivity(
|
||||
activity));
|
||||
|
||||
if (mTaskbarActivityContext != null) {
|
||||
mTaskbarActivityContext.setUIController(mLauncher == null
|
||||
? TaskbarUIController.DEFAULT
|
||||
: new LauncherTaskbarUIController(launcher, mTaskbarActivityContext));
|
||||
mTaskbarActivityContext.setUIController(
|
||||
createTaskbarUIControllerForActivity(mActivity));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link UnfoldTransitionProgressProvider} to use while the given StatefulActivity
|
||||
* is active.
|
||||
*/
|
||||
private UnfoldTransitionProgressProvider getUnfoldTransitionProgressProviderForActivity(
|
||||
StatefulActivity activity) {
|
||||
if (activity instanceof BaseQuickstepLauncher) {
|
||||
return ((BaseQuickstepLauncher) activity).getUnfoldTransitionProgressProvider();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link TaskbarUIController} to use while the given StatefulActivity is active.
|
||||
*/
|
||||
private TaskbarUIController createTaskbarUIControllerForActivity(StatefulActivity activity) {
|
||||
if (activity instanceof BaseQuickstepLauncher) {
|
||||
return new LauncherTaskbarUIController((BaseQuickstepLauncher) activity);
|
||||
}
|
||||
if (activity instanceof RecentsActivity) {
|
||||
return new FallbackTaskbarUIController((RecentsActivity) activity);
|
||||
}
|
||||
return TaskbarUIController.DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears a previously set {@link StatefulActivity}
|
||||
*/
|
||||
public void clearActivity(@NonNull StatefulActivity activity) {
|
||||
if (mActivity == activity) {
|
||||
mActivity = null;
|
||||
if (mTaskbarActivityContext != null) {
|
||||
mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
|
||||
}
|
||||
mUnfoldProgressProvider.setSourceProvider(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void recreateTaskbar() {
|
||||
destroyExistingTaskbar();
|
||||
if (!FeatureFlags.ENABLE_TASKBAR.get()) {
|
||||
|
||||
DeviceProfile dp =
|
||||
mUserUnlocked ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
|
||||
|
||||
boolean isTaskBarEnabled =
|
||||
FeatureFlags.ENABLE_TASKBAR.get() && dp != null && dp.isTaskbarPresent;
|
||||
|
||||
if (!isTaskBarEnabled) {
|
||||
SystemUiProxy.INSTANCE.get(mContext)
|
||||
.notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
|
||||
return;
|
||||
}
|
||||
if (!mUserUnlocked) {
|
||||
return;
|
||||
}
|
||||
DeviceProfile dp = LauncherAppState.getIDP(mContext).getDeviceProfile(mContext);
|
||||
if (!dp.isTaskbarPresent) {
|
||||
return;
|
||||
}
|
||||
mTaskbarActivityContext = new TaskbarActivityContext(
|
||||
mContext, dp.copy(mContext), mNavButtonController);
|
||||
mTaskbarActivityContext.init();
|
||||
if (mLauncher != null) {
|
||||
|
||||
mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp.copy(mContext),
|
||||
mNavButtonController, mUnfoldProgressProvider);
|
||||
|
||||
mTaskbarActivityContext.init(mSharedState);
|
||||
if (mActivity != null) {
|
||||
mTaskbarActivityContext.setUIController(
|
||||
new LauncherTaskbarUIController(mLauncher, mTaskbarActivityContext));
|
||||
createTaskbarUIControllerForActivity(mActivity));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags}
|
||||
* @param systemUiStateFlags The latest SystemUiStateFlags
|
||||
*/
|
||||
public void onSystemUiFlagsChanged(int systemUiStateFlags) {
|
||||
boolean isImeVisible = (systemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
|
||||
mSharedState.sysuiStateFlags = systemUiStateFlags;
|
||||
if (mTaskbarActivityContext != null) {
|
||||
mTaskbarActivityContext.setImeIsVisible(isImeVisible);
|
||||
mTaskbarActivityContext.updateSysuiStateFlags(systemUiStateFlags, false /* fromInit */);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from
|
||||
* instantiating at all, which is what's responsible for sending sysui state flags over.
|
||||
*
|
||||
* @param vis IME visibility flag
|
||||
* @param backDisposition Used to determine back button behavior for software keyboard
|
||||
* See BACK_DISPOSITION_* constants in {@link InputMethodService}
|
||||
* Sets the flag indicating setup UI is visible
|
||||
*/
|
||||
public void updateImeStatus(int displayId, int vis, int backDisposition,
|
||||
boolean showImeSwitcher) {
|
||||
public void setSetupUIVisible(boolean isVisible) {
|
||||
mSharedState.setupUIVisible = isVisible;
|
||||
if (mTaskbarActivityContext != null) {
|
||||
mTaskbarActivityContext.updateImeStatus(displayId, vis, showImeSwitcher);
|
||||
mTaskbarActivityContext.setSetupUIVisible(isVisible);
|
||||
}
|
||||
}
|
||||
|
||||
public void onRotationProposal(int rotation, boolean isValid) {
|
||||
if (mTaskbarActivityContext != null) {
|
||||
mTaskbarActivityContext.onRotationProposal(rotation, isValid);
|
||||
}
|
||||
}
|
||||
|
||||
public void disableNavBarElements(int displayId, int state1, int state2, boolean animate) {
|
||||
if (mTaskbarActivityContext != null) {
|
||||
mTaskbarActivityContext.disableNavBarElements(displayId, state1, state2, animate);
|
||||
}
|
||||
}
|
||||
|
||||
public void onSystemBarAttributesChanged(int displayId, int behavior) {
|
||||
if (mTaskbarActivityContext != null) {
|
||||
mTaskbarActivityContext.onSystemBarAttributesChanged(displayId, behavior);
|
||||
}
|
||||
}
|
||||
|
||||
public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
|
||||
if (mTaskbarActivityContext != null) {
|
||||
mTaskbarActivityContext.onNavButtonsDarkIntensityChanged(darkIntensity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,5 +286,13 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen
|
||||
destroyExistingTaskbar();
|
||||
mDisplayController.removeChangeListener(this);
|
||||
mSysUINavigationMode.removeModeChangeListener(this);
|
||||
SettingsCache.INSTANCE.get(mContext).unregister(USER_SETUP_COMPLETE_URI,
|
||||
mUserSetupCompleteListener);
|
||||
mContext.unregisterComponentCallbacks(mComponentCallbacks);
|
||||
mContext.unregisterReceiver(mShutdownReceiver);
|
||||
}
|
||||
|
||||
public @Nullable TaskbarActivityContext getCurrentActivityContext() {
|
||||
return mTaskbarActivityContext;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.model.BgDataModel;
|
||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.IntArray;
|
||||
import com.android.launcher3.util.IntSet;
|
||||
import com.android.launcher3.util.ItemInfoMatcher;
|
||||
import com.android.launcher3.util.LauncherBindableItemsContainer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Launcher model Callbacks for rendering taskbar.
|
||||
*/
|
||||
public class TaskbarModelCallbacks implements
|
||||
BgDataModel.Callbacks, LauncherBindableItemsContainer {
|
||||
|
||||
private final SparseArray<ItemInfo> mHotseatItems = new SparseArray<>();
|
||||
private List<ItemInfo> mPredictedItems = Collections.emptyList();
|
||||
|
||||
private final TaskbarActivityContext mContext;
|
||||
private final TaskbarView mContainer;
|
||||
|
||||
// Initialized in init.
|
||||
private TaskbarControllers mControllers;
|
||||
|
||||
private boolean mBindInProgress = false;
|
||||
|
||||
public TaskbarModelCallbacks(
|
||||
TaskbarActivityContext context, TaskbarView container) {
|
||||
mContext = context;
|
||||
mContainer = container;
|
||||
}
|
||||
|
||||
public void init(TaskbarControllers controllers) {
|
||||
mControllers = controllers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startBinding() {
|
||||
mBindInProgress = true;
|
||||
mHotseatItems.clear();
|
||||
mPredictedItems = Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishBindingItems(IntSet pagesBoundFirst) {
|
||||
mBindInProgress = false;
|
||||
commitItemsToUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
|
||||
ArrayList<ItemInfo> addAnimated) {
|
||||
boolean add1 = handleItemsAdded(addNotAnimated);
|
||||
boolean add2 = handleItemsAdded(addAnimated);
|
||||
if (add1 || add2) {
|
||||
commitItemsToUI();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) {
|
||||
if (handleItemsAdded(shortcuts)) {
|
||||
commitItemsToUI();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleItemsAdded(List<ItemInfo> items) {
|
||||
boolean modified = false;
|
||||
for (ItemInfo item : items) {
|
||||
if (item.container == Favorites.CONTAINER_HOTSEAT) {
|
||||
mHotseatItems.put(item.screenId, item);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
|
||||
updateWorkspaceItems(updated, mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindRestoreItemsChange(HashSet<ItemInfo> updates) {
|
||||
updateRestoreItems(updates, mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mapOverItems(ItemOperator op) {
|
||||
final int itemCount = mContainer.getChildCount();
|
||||
for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
|
||||
View item = mContainer.getChildAt(itemIdx);
|
||||
if (op.evaluate((ItemInfo) item.getTag(), item)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) {
|
||||
if (handleItemsRemoved(matcher)) {
|
||||
commitItemsToUI();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleItemsRemoved(ItemInfoMatcher matcher) {
|
||||
boolean modified = false;
|
||||
for (int i = mHotseatItems.size() - 1; i >= 0; i--) {
|
||||
if (matcher.matchesInfo(mHotseatItems.valueAt(i))) {
|
||||
modified = true;
|
||||
mHotseatItems.removeAt(i);
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindItemsModified(List<ItemInfo> items) {
|
||||
boolean removed = handleItemsRemoved(ItemInfoMatcher.ofItems(items));
|
||||
boolean added = handleItemsAdded(items);
|
||||
if (removed || added) {
|
||||
commitItemsToUI();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindExtraContainerItems(FixedContainerItems item) {
|
||||
if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
|
||||
mPredictedItems = item.items;
|
||||
commitItemsToUI();
|
||||
}
|
||||
}
|
||||
|
||||
private void commitItemsToUI() {
|
||||
if (mBindInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemInfo[] hotseatItemInfos =
|
||||
new ItemInfo[mContext.getDeviceProfile().numShownHotseatIcons];
|
||||
int predictionSize = mPredictedItems.size();
|
||||
int predictionNextIndex = 0;
|
||||
|
||||
boolean isHotseatEmpty = true;
|
||||
for (int i = 0; i < hotseatItemInfos.length; i++) {
|
||||
hotseatItemInfos[i] = mHotseatItems.get(i);
|
||||
if (hotseatItemInfos[i] == null && predictionNextIndex < predictionSize) {
|
||||
hotseatItemInfos[i] = mPredictedItems.get(predictionNextIndex);
|
||||
hotseatItemInfos[i].screenId = i;
|
||||
predictionNextIndex++;
|
||||
}
|
||||
if (hotseatItemInfos[i] != null) {
|
||||
isHotseatEmpty = false;
|
||||
}
|
||||
}
|
||||
mContainer.updateHotseatItems(hotseatItemInfos);
|
||||
|
||||
final boolean finalIsHotseatEmpty = isHotseatEmpty;
|
||||
mControllers.runAfterInit(() -> {
|
||||
mControllers.taskbarStashController.updateStateForFlag(
|
||||
TaskbarStashController.FLAG_STASHED_IN_APP_EMPTY, finalIsHotseatEmpty);
|
||||
mControllers.taskbarStashController.applyState();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) {
|
||||
mControllers.taskbarPopupController.setDeepShortcutMap(deepShortcutMapCopy);
|
||||
}
|
||||
}
|
||||
@@ -16,13 +16,18 @@
|
||||
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
|
||||
import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.TestProtocol;
|
||||
import com.android.quickstep.OverviewCommandHelper;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.TouchInteractionService;
|
||||
@@ -34,17 +39,23 @@ import java.lang.annotation.RetentionPolicy;
|
||||
* Controller for 3 button mode in the taskbar.
|
||||
* Handles all the functionality of the various buttons, making/routing the right calls into
|
||||
* launcher or sysui/system.
|
||||
*
|
||||
* TODO: Create callbacks to hook into UI layer since state will change for more context buttons/
|
||||
* assistant invocation.
|
||||
*/
|
||||
public class TaskbarNavButtonController {
|
||||
|
||||
/** Allow some time in between the long press for back and recents. */
|
||||
static final int SCREEN_PIN_LONG_PRESS_THRESHOLD = 200;
|
||||
static final int SCREEN_PIN_LONG_PRESS_RESET = SCREEN_PIN_LONG_PRESS_THRESHOLD + 100;
|
||||
|
||||
private long mLastScreenPinLongPress;
|
||||
private boolean mScreenPinned;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(value = {
|
||||
BUTTON_BACK,
|
||||
BUTTON_HOME,
|
||||
BUTTON_RECENTS,
|
||||
BUTTON_IME_SWITCH
|
||||
BUTTON_IME_SWITCH,
|
||||
BUTTON_A11Y,
|
||||
})
|
||||
|
||||
public @interface TaskbarButton {}
|
||||
@@ -53,11 +64,22 @@ public class TaskbarNavButtonController {
|
||||
static final int BUTTON_HOME = BUTTON_BACK << 1;
|
||||
static final int BUTTON_RECENTS = BUTTON_HOME << 1;
|
||||
static final int BUTTON_IME_SWITCH = BUTTON_RECENTS << 1;
|
||||
static final int BUTTON_A11Y = BUTTON_IME_SWITCH << 1;
|
||||
|
||||
private static final int SCREEN_UNPIN_COMBO = BUTTON_BACK | BUTTON_RECENTS;
|
||||
private int mLongPressedButtons = 0;
|
||||
|
||||
private final TouchInteractionService mService;
|
||||
private final SystemUiProxy mSystemUiProxy;
|
||||
private final Handler mHandler;
|
||||
|
||||
public TaskbarNavButtonController(TouchInteractionService service) {
|
||||
private final Runnable mResetLongPress = this::resetScreenUnpin;
|
||||
|
||||
public TaskbarNavButtonController(TouchInteractionService service,
|
||||
SystemUiProxy systemUiProxy, Handler handler) {
|
||||
mService = service;
|
||||
mSystemUiProxy = systemUiProxy;
|
||||
mHandler = handler;
|
||||
}
|
||||
|
||||
public void onButtonClick(@TaskbarButton int buttonType) {
|
||||
@@ -69,32 +91,115 @@ public class TaskbarNavButtonController {
|
||||
navigateHome();
|
||||
break;
|
||||
case BUTTON_RECENTS:
|
||||
navigateToOverview();;
|
||||
navigateToOverview();
|
||||
break;
|
||||
case BUTTON_IME_SWITCH:
|
||||
showIMESwitcher();
|
||||
break;
|
||||
case BUTTON_A11Y:
|
||||
notifyA11yClick(false /* longClick */);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onButtonLongClick(@TaskbarButton int buttonType) {
|
||||
switch (buttonType) {
|
||||
case BUTTON_HOME:
|
||||
startAssistant();
|
||||
return true;
|
||||
case BUTTON_A11Y:
|
||||
notifyA11yClick(true /* longClick */);
|
||||
return true;
|
||||
case BUTTON_BACK:
|
||||
case BUTTON_RECENTS:
|
||||
mLongPressedButtons |= buttonType;
|
||||
return determineScreenUnpin();
|
||||
case BUTTON_IME_SWITCH:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user has long pressed back and recents buttons
|
||||
* "together" (within {@link #SCREEN_PIN_LONG_PRESS_THRESHOLD})ms
|
||||
* If so, then requests the system to turn off screen pinning.
|
||||
*
|
||||
* @return true if the long press is a valid user action in attempting to unpin an app
|
||||
* Will always return {@code false} when screen pinning is not active.
|
||||
* NOTE: Returning true does not mean that screen pinning has stopped
|
||||
*/
|
||||
private boolean determineScreenUnpin() {
|
||||
long timeNow = System.currentTimeMillis();
|
||||
if (!mScreenPinned) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mLastScreenPinLongPress == 0) {
|
||||
// First button long press registered, just mark time and wait for second button press
|
||||
mLastScreenPinLongPress = System.currentTimeMillis();
|
||||
mHandler.postDelayed(mResetLongPress, SCREEN_PIN_LONG_PRESS_RESET);
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((timeNow - mLastScreenPinLongPress) > SCREEN_PIN_LONG_PRESS_THRESHOLD) {
|
||||
// Too long in-between presses, reset the clock
|
||||
resetScreenUnpin();
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((mLongPressedButtons & SCREEN_UNPIN_COMBO) == SCREEN_UNPIN_COMBO) {
|
||||
// Hooray! They did it (finally...)
|
||||
mSystemUiProxy.stopScreenPinning();
|
||||
mHandler.removeCallbacks(mResetLongPress);
|
||||
resetScreenUnpin();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void resetScreenUnpin() {
|
||||
mLongPressedButtons = 0;
|
||||
mLastScreenPinLongPress = 0;
|
||||
}
|
||||
|
||||
public void updateSysuiFlags(int sysuiFlags) {
|
||||
mScreenPinned = (sysuiFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
|
||||
}
|
||||
|
||||
private void navigateHome() {
|
||||
mService.startActivity(new Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_HOME)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_HOME);
|
||||
}
|
||||
|
||||
private void navigateToOverview() {
|
||||
mService.getOverviewCommandHelper()
|
||||
.addCommand(OverviewCommandHelper.TYPE_SHOW);
|
||||
if (mScreenPinned) {
|
||||
return;
|
||||
}
|
||||
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
|
||||
mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_TOGGLE);
|
||||
}
|
||||
|
||||
private void executeBack() {
|
||||
SystemUiProxy.INSTANCE.getNoCreate().onBackPressed();
|
||||
mSystemUiProxy.onBackPressed();
|
||||
}
|
||||
|
||||
private void showIMESwitcher() {
|
||||
mService.getSystemService(InputMethodManager.class)
|
||||
.showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */,
|
||||
DEFAULT_DISPLAY);
|
||||
mSystemUiProxy.onImeSwitcherPressed();
|
||||
}
|
||||
|
||||
private void notifyA11yClick(boolean longClick) {
|
||||
if (longClick) {
|
||||
mSystemUiProxy.notifyAccessibilityButtonLongClicked();
|
||||
} else {
|
||||
mSystemUiProxy.notifyAccessibilityButtonClicked(mService.getDisplayId());
|
||||
}
|
||||
}
|
||||
|
||||
private void startAssistant() {
|
||||
if (mScreenPinned) {
|
||||
return;
|
||||
}
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
|
||||
mSystemUiProxy.startAssistant(args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.popup.PopupContainerWithArrow;
|
||||
import com.android.launcher3.popup.PopupDataProvider;
|
||||
import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Implements interfaces required to show and allow interacting with a PopupContainerWithArrow.
|
||||
*/
|
||||
public class TaskbarPopupController {
|
||||
|
||||
private static final SystemShortcut.Factory<TaskbarActivityContext>
|
||||
APP_INFO = SystemShortcut.AppInfo::new;
|
||||
|
||||
private final PopupDataProvider mPopupDataProvider;
|
||||
|
||||
public TaskbarPopupController() {
|
||||
// TODO (b/198438631): add notifications dots change listener
|
||||
mPopupDataProvider = new PopupDataProvider(packageUserKey -> {});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public PopupDataProvider getPopupDataProvider() {
|
||||
return mPopupDataProvider;
|
||||
}
|
||||
|
||||
public void setDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) {
|
||||
mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the notifications and deep shortcuts associated with a Taskbar {@param icon}.
|
||||
* @return the container if shown or null.
|
||||
*/
|
||||
public PopupContainerWithArrow<TaskbarActivityContext> showForIcon(BubbleTextView icon) {
|
||||
TaskbarActivityContext context = ActivityContext.lookupContext(icon.getContext());
|
||||
if (PopupContainerWithArrow.getOpen(context) != null) {
|
||||
// There is already an items container open, so don't open this one.
|
||||
icon.clearFocus();
|
||||
return null;
|
||||
}
|
||||
ItemInfo item = (ItemInfo) icon.getTag();
|
||||
if (!PopupContainerWithArrow.canShow(icon, item)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final PopupContainerWithArrow<TaskbarActivityContext> container =
|
||||
(PopupContainerWithArrow) context.getLayoutInflater().inflate(
|
||||
R.layout.popup_container, context.getDragLayer(), false);
|
||||
// TODO (b/198438631): configure for taskbar/context
|
||||
|
||||
container.populateAndShow(icon,
|
||||
mPopupDataProvider.getShortcutCountForItem(item),
|
||||
mPopupDataProvider.getNotificationKeysForItem(item),
|
||||
// TODO (b/198438631): add support for INSTALL shortcut factory
|
||||
Stream.of(APP_INFO)
|
||||
.map(s -> s.getShortcut(context, item))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList()));
|
||||
container.requestFocus();
|
||||
return container;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* View that handles scrimming the taskbar and the inverted corners it draws. The scrim is used
|
||||
* when bubbles is expanded.
|
||||
*/
|
||||
public class TaskbarScrimView extends View {
|
||||
private final Paint mTaskbarScrimPaint;
|
||||
private final Path mInvertedLeftCornerPath, mInvertedRightCornerPath;
|
||||
|
||||
private boolean mShowScrim;
|
||||
private float mLeftCornerRadius, mRightCornerRadius;
|
||||
private float mBackgroundHeight;
|
||||
|
||||
public TaskbarScrimView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public TaskbarScrimView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public TaskbarScrimView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public TaskbarScrimView(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
|
||||
mTaskbarScrimPaint = new Paint();
|
||||
mTaskbarScrimPaint.setColor(getResources().getColor(android.R.color.system_neutral1_1000));
|
||||
mTaskbarScrimPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
|
||||
mTaskbarScrimPaint.setStyle(Paint.Style.FILL);
|
||||
|
||||
mInvertedLeftCornerPath = new Path();
|
||||
mInvertedRightCornerPath = new Path();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
if (mShowScrim) {
|
||||
canvas.save();
|
||||
canvas.translate(0, canvas.getHeight() - mBackgroundHeight);
|
||||
|
||||
// Scrim the taskbar itself.
|
||||
canvas.drawRect(0, 0, canvas.getWidth(), mBackgroundHeight, mTaskbarScrimPaint);
|
||||
|
||||
// Scrim the inverted rounded corners above the taskbar.
|
||||
canvas.translate(0, -mLeftCornerRadius);
|
||||
canvas.drawPath(mInvertedLeftCornerPath, mTaskbarScrimPaint);
|
||||
canvas.translate(0, mLeftCornerRadius);
|
||||
canvas.translate(canvas.getWidth() - mRightCornerRadius, -mRightCornerRadius);
|
||||
canvas.drawPath(mInvertedRightCornerPath, mTaskbarScrimPaint);
|
||||
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the height of the taskbar background.
|
||||
* @param height the height of the background.
|
||||
*/
|
||||
protected void setBackgroundHeight(float height) {
|
||||
mBackgroundHeight = height;
|
||||
if (mShowScrim) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the alpha of the taskbar scrim.
|
||||
* @param alpha the alpha of the scrim.
|
||||
*/
|
||||
protected void setScrimAlpha(float alpha) {
|
||||
mShowScrim = alpha > 0f;
|
||||
mTaskbarScrimPaint.setAlpha((int) (alpha * 255));
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the radius of the left and right corners above the taskbar.
|
||||
* @param leftCornerRadius the radius of the left corner.
|
||||
* @param rightCornerRadius the radius of the right corner.
|
||||
*/
|
||||
protected void setCornerSizes(float leftCornerRadius, float rightCornerRadius) {
|
||||
mLeftCornerRadius = leftCornerRadius;
|
||||
mRightCornerRadius = rightCornerRadius;
|
||||
|
||||
Path square = new Path();
|
||||
square.addRect(0, 0, mLeftCornerRadius, mLeftCornerRadius, Path.Direction.CW);
|
||||
Path circle = new Path();
|
||||
circle.addCircle(mLeftCornerRadius, 0, mLeftCornerRadius, Path.Direction.CW);
|
||||
mInvertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE);
|
||||
square.reset();
|
||||
square.addRect(0, 0, mRightCornerRadius, mRightCornerRadius, Path.Direction.CW);
|
||||
circle.reset();
|
||||
circle.addCircle(0, 0, mRightCornerRadius, Path.Direction.CW);
|
||||
mInvertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE);
|
||||
|
||||
if (mShowScrim) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.PathInterpolator;
|
||||
|
||||
import com.android.quickstep.AnimatedFloat;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
|
||||
/**
|
||||
* Handles properties/data collection, and passes the results to {@link TaskbarScrimView} to render.
|
||||
*/
|
||||
public class TaskbarScrimViewController {
|
||||
|
||||
private static final float SCRIM_ALPHA = 0.6f;
|
||||
|
||||
private static final Interpolator SCRIM_ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
|
||||
private static final Interpolator SCRIM_ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
|
||||
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final TaskbarScrimView mScrimView;
|
||||
|
||||
// Alpha property for the scrim.
|
||||
private final AnimatedFloat mScrimAlpha = new AnimatedFloat(this::updateScrimAlpha);
|
||||
|
||||
// Initialized in init.
|
||||
private TaskbarControllers mControllers;
|
||||
|
||||
public TaskbarScrimViewController(TaskbarActivityContext activity, TaskbarScrimView scrimView) {
|
||||
mActivity = activity;
|
||||
mScrimView = scrimView;
|
||||
mScrimView.setCornerSizes(mActivity.getLeftCornerRadius(),
|
||||
mActivity.getRightCornerRadius());
|
||||
mScrimView.setBackgroundHeight(mActivity.getDeviceProfile().taskbarSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the controller
|
||||
*/
|
||||
public void init(TaskbarControllers controllers) {
|
||||
mControllers = controllers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the scrim state based on the flags.
|
||||
*/
|
||||
public void updateStateForSysuiFlags(int stateFlags, boolean skipAnim) {
|
||||
final boolean bubblesExpanded = (stateFlags & SYSUI_STATE_BUBBLES_EXPANDED) != 0;
|
||||
final boolean manageMenuExpanded =
|
||||
(stateFlags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
|
||||
final boolean showScrim = !mControllers.navbarButtonsViewController.isImeVisible()
|
||||
&& bubblesExpanded && mControllers.taskbarStashController.isInAppAndNotStashed();
|
||||
final float scrimAlpha = manageMenuExpanded
|
||||
// When manage menu shows there's the first scrim and second scrim so figure out
|
||||
// what the total transparency would be.
|
||||
? (SCRIM_ALPHA + (SCRIM_ALPHA * (1 - SCRIM_ALPHA)))
|
||||
: showScrim ? SCRIM_ALPHA : 0;
|
||||
showScrim(showScrim, scrimAlpha, skipAnim);
|
||||
}
|
||||
|
||||
private void showScrim(boolean showScrim, float alpha, boolean skipAnim) {
|
||||
mScrimView.setOnClickListener(showScrim ? (view) -> onClick() : null);
|
||||
mScrimView.setClickable(showScrim);
|
||||
ObjectAnimator anim = mScrimAlpha.animateToValue(showScrim ? alpha : 0);
|
||||
anim.setInterpolator(showScrim ? SCRIM_ALPHA_IN : SCRIM_ALPHA_OUT);
|
||||
anim.start();
|
||||
if (skipAnim) {
|
||||
anim.end();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateScrimAlpha() {
|
||||
mScrimView.setScrimAlpha(mScrimAlpha.value);
|
||||
}
|
||||
|
||||
private void onClick() {
|
||||
SystemUiProxy.INSTANCE.get(mActivity).onBackPressed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
/**
|
||||
* State shared across different taskbar instance
|
||||
*/
|
||||
public class TaskbarSharedState {
|
||||
|
||||
public int sysuiStateFlags;
|
||||
|
||||
public boolean setupUIVisible = false;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,592 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import static android.view.HapticFeedbackConstants.LONG_PRESS;
|
||||
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
|
||||
import com.android.quickstep.AnimatedFloat;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
|
||||
import java.util.function.IntPredicate;
|
||||
|
||||
/**
|
||||
* Coordinates between controllers such as TaskbarViewController and StashedHandleViewController to
|
||||
* create a cohesive animation between stashed/unstashed states.
|
||||
*/
|
||||
public class TaskbarStashController {
|
||||
|
||||
public static final int FLAG_IN_APP = 1 << 0;
|
||||
public static final int FLAG_STASHED_IN_APP_MANUAL = 1 << 1; // long press, persisted
|
||||
public static final int FLAG_STASHED_IN_APP_PINNED = 1 << 2; // app pinning
|
||||
public static final int FLAG_STASHED_IN_APP_EMPTY = 1 << 3; // no hotseat icons
|
||||
public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 4; // setup wizard and AllSetActivity
|
||||
public static final int FLAG_STASHED_IN_APP_IME = 1 << 5; // IME is visible
|
||||
public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 6;
|
||||
|
||||
// If we're in an app and any of these flags are enabled, taskbar should be stashed.
|
||||
private static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL
|
||||
| FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP
|
||||
| FLAG_STASHED_IN_APP_IME;
|
||||
|
||||
// If any of these flags are enabled, inset apps by our stashed height instead of our unstashed
|
||||
// height. This way the reported insets are consistent even during transitions out of the app.
|
||||
// Currently any flag that causes us to stash in an app is included, except for IME since that
|
||||
// covers the underlying app anyway and thus the app shouldn't change insets.
|
||||
private static final int FLAGS_REPORT_STASHED_INSETS_TO_APP = FLAGS_STASHED_IN_APP
|
||||
& ~FLAG_STASHED_IN_APP_IME;
|
||||
|
||||
/**
|
||||
* How long to stash/unstash when manually invoked via long press.
|
||||
*/
|
||||
public static final long TASKBAR_STASH_DURATION = 300;
|
||||
|
||||
/**
|
||||
* How long to stash/unstash when keyboard is appearing/disappearing.
|
||||
*/
|
||||
private static final long TASKBAR_STASH_DURATION_FOR_IME = 80;
|
||||
|
||||
/**
|
||||
* The scale TaskbarView animates to when being stashed.
|
||||
*/
|
||||
private static final float STASHED_TASKBAR_SCALE = 0.5f;
|
||||
|
||||
/**
|
||||
* How long the hint animation plays, starting on motion down.
|
||||
*/
|
||||
private static final long TASKBAR_HINT_STASH_DURATION =
|
||||
ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
|
||||
|
||||
/**
|
||||
* The scale that TaskbarView animates to when hinting towards the stashed state.
|
||||
*/
|
||||
private static final float STASHED_TASKBAR_HINT_SCALE = 0.9f;
|
||||
|
||||
/**
|
||||
* The scale that the stashed handle animates to when hinting towards the unstashed state.
|
||||
*/
|
||||
private static final float UNSTASHED_TASKBAR_HANDLE_HINT_SCALE = 1.1f;
|
||||
|
||||
/**
|
||||
* The SharedPreferences key for whether user has manually stashed the taskbar.
|
||||
*/
|
||||
private static final String SHARED_PREFS_STASHED_KEY = "taskbar_is_stashed";
|
||||
|
||||
/**
|
||||
* Whether taskbar should be stashed out of the box.
|
||||
*/
|
||||
private static final boolean DEFAULT_STASHED_PREF = false;
|
||||
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final SharedPreferences mPrefs;
|
||||
private final int mStashedHeight;
|
||||
private final int mUnstashedHeight;
|
||||
private final SystemUiProxy mSystemUiProxy;
|
||||
|
||||
// Initialized in init.
|
||||
private TaskbarControllers mControllers;
|
||||
// Taskbar background properties.
|
||||
private AnimatedFloat mTaskbarBackgroundOffset;
|
||||
private AnimatedFloat mTaskbarImeBgAlpha;
|
||||
// TaskbarView icon properties.
|
||||
private AlphaProperty mIconAlphaForStash;
|
||||
private AnimatedFloat mIconScaleForStash;
|
||||
private AnimatedFloat mIconTranslationYForStash;
|
||||
// Stashed handle properties.
|
||||
private AlphaProperty mTaskbarStashedHandleAlpha;
|
||||
private AnimatedFloat mTaskbarStashedHandleHintScale;
|
||||
|
||||
/** Whether we are currently visually stashed (might change based on launcher state). */
|
||||
private boolean mIsStashed = false;
|
||||
private int mState;
|
||||
|
||||
private @Nullable AnimatorSet mAnimator;
|
||||
private boolean mIsSystemGestureInProgress;
|
||||
private boolean mIsImeShowing;
|
||||
|
||||
// Evaluate whether the handle should be stashed
|
||||
private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
|
||||
flags -> {
|
||||
boolean inApp = hasAnyFlag(flags, FLAG_IN_APP);
|
||||
boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP);
|
||||
boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE);
|
||||
return (inApp && stashedInApp) || (!inApp && stashedLauncherState);
|
||||
});
|
||||
|
||||
public TaskbarStashController(TaskbarActivityContext activity) {
|
||||
mActivity = activity;
|
||||
mPrefs = Utilities.getPrefs(mActivity);
|
||||
final Resources resources = mActivity.getResources();
|
||||
mStashedHeight = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
|
||||
mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
|
||||
mUnstashedHeight = mActivity.getDeviceProfile().taskbarSize;
|
||||
}
|
||||
|
||||
public void init(TaskbarControllers controllers, TaskbarSharedState sharedState) {
|
||||
mControllers = controllers;
|
||||
|
||||
TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController;
|
||||
mTaskbarBackgroundOffset = dragLayerController.getTaskbarBackgroundOffset();
|
||||
mTaskbarImeBgAlpha = dragLayerController.getImeBgTaskbar();
|
||||
|
||||
TaskbarViewController taskbarViewController = controllers.taskbarViewController;
|
||||
mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().getProperty(
|
||||
TaskbarViewController.ALPHA_INDEX_STASH);
|
||||
mIconScaleForStash = taskbarViewController.getTaskbarIconScaleForStash();
|
||||
mIconTranslationYForStash = taskbarViewController.getTaskbarIconTranslationYForStash();
|
||||
|
||||
StashedHandleViewController stashedHandleController =
|
||||
controllers.stashedHandleViewController;
|
||||
mTaskbarStashedHandleAlpha = stashedHandleController.getStashedHandleAlpha().getProperty(
|
||||
StashedHandleViewController.ALPHA_INDEX_STASHED);
|
||||
mTaskbarStashedHandleHintScale = stashedHandleController.getStashedHandleHintScale();
|
||||
|
||||
boolean isManuallyStashedInApp = supportsManualStashing()
|
||||
&& mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
|
||||
boolean isInSetup = !mActivity.isUserSetupComplete() || sharedState.setupUIVisible;
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
|
||||
// TODO(b/204384193): Temporarily disable SUW specific logic
|
||||
// updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup);
|
||||
if (isInSetup) {
|
||||
// Update the in-app state to ensure isStashed() reflects right state during SUW
|
||||
updateStateForFlag(FLAG_IN_APP, true);
|
||||
}
|
||||
applyState();
|
||||
|
||||
notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the taskbar can visually stash into a handle based on the current device
|
||||
* state.
|
||||
*/
|
||||
private boolean supportsVisualStashing() {
|
||||
return !mActivity.isThreeButtonNav();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the user can manually stash the taskbar based on the current device state.
|
||||
*/
|
||||
private boolean supportsManualStashing() {
|
||||
return supportsVisualStashing()
|
||||
&& (!Utilities.IS_RUNNING_IN_TEST_HARNESS || supportsStashingForTests());
|
||||
}
|
||||
|
||||
private boolean supportsStashingForTests() {
|
||||
// TODO: enable this for tests that specifically check stash/unstash behavior.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the flag indicating setup UI is visible
|
||||
*/
|
||||
protected void setSetupUIVisible(boolean isVisible) {
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_SETUP,
|
||||
isVisible || !mActivity.isUserSetupComplete());
|
||||
applyState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the taskbar is currently visually stashed.
|
||||
*/
|
||||
public boolean isStashed() {
|
||||
return mIsStashed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the taskbar should be stashed in apps (e.g. user long pressed to stash).
|
||||
*/
|
||||
public boolean isStashedInApp() {
|
||||
return hasAnyFlag(FLAGS_STASHED_IN_APP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the taskbar should be stashed in the current LauncherState.
|
||||
*/
|
||||
public boolean isInStashedLauncherState() {
|
||||
return hasAnyFlag(FLAG_IN_STASHED_LAUNCHER_STATE) && supportsVisualStashing();
|
||||
}
|
||||
|
||||
private boolean hasAnyFlag(int flagMask) {
|
||||
return hasAnyFlag(mState, flagMask);
|
||||
}
|
||||
|
||||
private boolean hasAnyFlag(int flags, int flagMask) {
|
||||
return (flags & flagMask) != 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the taskbar is currently visible and in an app.
|
||||
*/
|
||||
public boolean isInAppAndNotStashed() {
|
||||
return !mIsStashed && (mState & FLAG_IN_APP) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height that taskbar will inset when inside apps.
|
||||
*/
|
||||
public int getContentHeightToReportToApps() {
|
||||
if (hasAnyFlag(FLAGS_REPORT_STASHED_INSETS_TO_APP)) {
|
||||
boolean isAnimating = mAnimator != null && mAnimator.isStarted();
|
||||
return mControllers.stashedHandleViewController.isStashedHandleVisible() || isAnimating
|
||||
? mStashedHeight : 0;
|
||||
}
|
||||
return mUnstashedHeight;
|
||||
}
|
||||
|
||||
public int getStashedHeight() {
|
||||
return mStashedHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when long pressing the nav region when taskbar is present.
|
||||
* @return Whether taskbar was stashed and now is unstashed.
|
||||
*/
|
||||
public boolean onLongPressToUnstashTaskbar() {
|
||||
if (!isStashed()) {
|
||||
// We only listen for long press on the nav region to unstash the taskbar. To stash the
|
||||
// taskbar, we use an OnLongClickListener on TaskbarView instead.
|
||||
return false;
|
||||
}
|
||||
if (updateAndAnimateIsManuallyStashedInApp(false)) {
|
||||
mControllers.taskbarActivityContext.getDragLayer().performHapticFeedback(LONG_PRESS);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates whether we should stash the taskbar when in apps, and animates to the changed state.
|
||||
* @return Whether we started an animation to either be newly stashed or unstashed.
|
||||
*/
|
||||
public boolean updateAndAnimateIsManuallyStashedInApp(boolean isManuallyStashedInApp) {
|
||||
if (!supportsManualStashing()) {
|
||||
return false;
|
||||
}
|
||||
if (hasAnyFlag(FLAG_STASHED_IN_APP_MANUAL) != isManuallyStashedInApp) {
|
||||
mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_KEY, isManuallyStashedInApp).apply();
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
|
||||
applyState();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stash animation and save to {@link #mAnimator}.
|
||||
* @param isStashed whether it's a stash animation or an unstash animation
|
||||
* @param duration duration of the animation
|
||||
* @param startDelay how many milliseconds to delay the animation after starting it.
|
||||
*/
|
||||
private void createAnimToIsStashed(boolean isStashed, long duration, long startDelay) {
|
||||
if (mAnimator != null) {
|
||||
mAnimator.cancel();
|
||||
}
|
||||
mAnimator = new AnimatorSet();
|
||||
|
||||
if (!supportsVisualStashing()) {
|
||||
// Just hide/show the icons and background instead of stashing into a handle.
|
||||
mAnimator.play(mIconAlphaForStash.animateToValue(isStashed ? 0 : 1)
|
||||
.setDuration(duration));
|
||||
mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
|
||||
hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
|
||||
mAnimator.setStartDelay(startDelay);
|
||||
mAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mAnimator = null;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
AnimatorSet fullLengthAnimatorSet = new AnimatorSet();
|
||||
// Not exactly half and may overlap. See [first|second]HalfDurationScale below.
|
||||
AnimatorSet firstHalfAnimatorSet = new AnimatorSet();
|
||||
AnimatorSet secondHalfAnimatorSet = new AnimatorSet();
|
||||
|
||||
final float firstHalfDurationScale;
|
||||
final float secondHalfDurationScale;
|
||||
|
||||
if (isStashed) {
|
||||
firstHalfDurationScale = 0.75f;
|
||||
secondHalfDurationScale = 0.5f;
|
||||
final float stashTranslation = (mUnstashedHeight - mStashedHeight) / 2f;
|
||||
|
||||
fullLengthAnimatorSet.playTogether(
|
||||
mTaskbarBackgroundOffset.animateToValue(1),
|
||||
mIconTranslationYForStash.animateToValue(stashTranslation)
|
||||
);
|
||||
firstHalfAnimatorSet.playTogether(
|
||||
mIconAlphaForStash.animateToValue(0),
|
||||
mIconScaleForStash.animateToValue(STASHED_TASKBAR_SCALE)
|
||||
);
|
||||
secondHalfAnimatorSet.playTogether(
|
||||
mTaskbarStashedHandleAlpha.animateToValue(1)
|
||||
);
|
||||
} else {
|
||||
firstHalfDurationScale = 0.5f;
|
||||
secondHalfDurationScale = 0.75f;
|
||||
|
||||
fullLengthAnimatorSet.playTogether(
|
||||
mTaskbarBackgroundOffset.animateToValue(0),
|
||||
mIconScaleForStash.animateToValue(1),
|
||||
mIconTranslationYForStash.animateToValue(0)
|
||||
);
|
||||
firstHalfAnimatorSet.playTogether(
|
||||
mTaskbarStashedHandleAlpha.animateToValue(0)
|
||||
);
|
||||
secondHalfAnimatorSet.playTogether(
|
||||
mIconAlphaForStash.animateToValue(1)
|
||||
);
|
||||
}
|
||||
|
||||
fullLengthAnimatorSet.play(mControllers.stashedHandleViewController
|
||||
.createRevealAnimToIsStashed(isStashed));
|
||||
// Return the stashed handle to its default scale in case it was changed as part of the
|
||||
// feedforward hint. Note that the reveal animation above also visually scales it.
|
||||
fullLengthAnimatorSet.play(mTaskbarStashedHandleHintScale.animateToValue(1f));
|
||||
|
||||
fullLengthAnimatorSet.setDuration(duration);
|
||||
firstHalfAnimatorSet.setDuration((long) (duration * firstHalfDurationScale));
|
||||
secondHalfAnimatorSet.setDuration((long) (duration * secondHalfDurationScale));
|
||||
secondHalfAnimatorSet.setStartDelay((long) (duration * (1 - secondHalfDurationScale)));
|
||||
|
||||
mAnimator.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
|
||||
secondHalfAnimatorSet);
|
||||
mAnimator.setStartDelay(startDelay);
|
||||
mAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mIsStashed = isStashed;
|
||||
onIsStashed(mIsStashed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mAnimator = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and starts a partial stash animation, hinting at the new state that will trigger when
|
||||
* long press is detected.
|
||||
* @param animateForward Whether we are going towards the new stashed state or returning to the
|
||||
* unstashed state.
|
||||
*/
|
||||
public void startStashHint(boolean animateForward) {
|
||||
if (isStashed() || !supportsManualStashing()) {
|
||||
// Already stashed, no need to hint in that direction.
|
||||
return;
|
||||
}
|
||||
mIconScaleForStash.animateToValue(
|
||||
animateForward ? STASHED_TASKBAR_HINT_SCALE : 1)
|
||||
.setDuration(TASKBAR_HINT_STASH_DURATION).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and starts a partial unstash animation, hinting at the new state that will trigger
|
||||
* when long press is detected.
|
||||
* @param animateForward Whether we are going towards the new unstashed state or returning to
|
||||
* the stashed state.
|
||||
*/
|
||||
public void startUnstashHint(boolean animateForward) {
|
||||
if (!isStashed()) {
|
||||
// Already unstashed, no need to hint in that direction.
|
||||
return;
|
||||
}
|
||||
mTaskbarStashedHandleHintScale.animateToValue(
|
||||
animateForward ? UNSTASHED_TASKBAR_HANDLE_HINT_SCALE : 1)
|
||||
.setDuration(TASKBAR_HINT_STASH_DURATION).start();
|
||||
}
|
||||
|
||||
private void onIsStashed(boolean isStashed) {
|
||||
mControllers.stashedHandleViewController.onIsStashed(isStashed);
|
||||
}
|
||||
|
||||
public void applyState() {
|
||||
applyState(TASKBAR_STASH_DURATION);
|
||||
}
|
||||
|
||||
public void applyState(long duration) {
|
||||
mStatePropertyHolder.setState(mState, duration, true);
|
||||
}
|
||||
|
||||
public void applyState(long duration, long startDelay) {
|
||||
mStatePropertyHolder.setState(mState, duration, startDelay, true);
|
||||
}
|
||||
|
||||
public Animator applyStateWithoutStart() {
|
||||
return applyStateWithoutStart(TASKBAR_STASH_DURATION);
|
||||
}
|
||||
|
||||
public Animator applyStateWithoutStart(long duration) {
|
||||
return mStatePropertyHolder.setState(mState, duration, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when a system gesture starts and settles, so we can defer updating
|
||||
* FLAG_STASHED_IN_APP_IME until after the gesture transition completes.
|
||||
*/
|
||||
public void setSystemGestureInProgress(boolean inProgress) {
|
||||
mIsSystemGestureInProgress = inProgress;
|
||||
// Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
|
||||
if (!mIsSystemGestureInProgress) {
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
|
||||
applyState(TASKBAR_STASH_DURATION_FOR_IME, getTaskbarStashStartDelayForIme());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When hiding the IME, delay the unstash animation to align with the end of the transition.
|
||||
*/
|
||||
private long getTaskbarStashStartDelayForIme() {
|
||||
if (mIsImeShowing) {
|
||||
// Only delay when IME is exiting, not entering.
|
||||
return 0;
|
||||
}
|
||||
// This duration is based on input_method_extract_exit.xml.
|
||||
long imeExitDuration = mControllers.taskbarActivityContext.getResources()
|
||||
.getInteger(android.R.integer.config_shortAnimTime);
|
||||
return imeExitDuration - TASKBAR_STASH_DURATION_FOR_IME;
|
||||
}
|
||||
|
||||
/** Called when some system ui state has changed. (See SYSUI_STATE_... in QuickstepContract) */
|
||||
public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) {
|
||||
long animDuration = TASKBAR_STASH_DURATION;
|
||||
long startDelay = 0;
|
||||
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_PINNED,
|
||||
hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING));
|
||||
|
||||
// Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
|
||||
mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
|
||||
if (!mIsSystemGestureInProgress) {
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
|
||||
animDuration = TASKBAR_STASH_DURATION_FOR_IME;
|
||||
startDelay = getTaskbarStashStartDelayForIme();
|
||||
}
|
||||
|
||||
applyState(skipAnim ? 0 : animDuration, skipAnim ? 0 : startDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the proper flag to indicate whether the task bar should be stashed.
|
||||
*
|
||||
* Note that this only updates the flag. {@link #applyState()} needs to be called separately.
|
||||
*
|
||||
* @param flag The flag to update.
|
||||
* @param enabled Whether to enable the flag: True will cause the task bar to be stashed /
|
||||
* unstashed.
|
||||
*/
|
||||
public void updateStateForFlag(int flag, boolean enabled) {
|
||||
if (enabled) {
|
||||
mState |= flag;
|
||||
} else {
|
||||
mState &= ~flag;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after updateStateForFlag() and applyState() have been called.
|
||||
* @param changedFlags The flags that have changed.
|
||||
*/
|
||||
private void onStateChangeApplied(int changedFlags) {
|
||||
if (hasAnyFlag(changedFlags, FLAGS_STASHED_IN_APP)) {
|
||||
mControllers.uiController.onStashedInAppChanged();
|
||||
}
|
||||
if (hasAnyFlag(changedFlags, FLAGS_STASHED_IN_APP | FLAG_IN_APP)) {
|
||||
notifyStashChange(/* visible */ hasAnyFlag(FLAG_IN_APP),
|
||||
/* stashed */ isStashedInApp());
|
||||
}
|
||||
if (hasAnyFlag(changedFlags, FLAG_STASHED_IN_APP_MANUAL)) {
|
||||
if (hasAnyFlag(FLAG_STASHED_IN_APP_MANUAL)) {
|
||||
mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_LONGPRESS_HIDE);
|
||||
} else {
|
||||
mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_LONGPRESS_SHOW);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyStashChange(boolean visible, boolean stashed) {
|
||||
mSystemUiProxy.notifyTaskbarStatus(visible, stashed);
|
||||
mControllers.rotationButtonController.onTaskbarStateChange(visible, stashed);
|
||||
}
|
||||
|
||||
private class StatePropertyHolder {
|
||||
private final IntPredicate mStashCondition;
|
||||
|
||||
private boolean mIsStashed;
|
||||
private int mPrevFlags;
|
||||
|
||||
StatePropertyHolder(IntPredicate stashCondition) {
|
||||
mStashCondition = stashCondition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #setState(int, long, long, boolean) with a default startDelay = 0.
|
||||
*/
|
||||
public Animator setState(int flags, long duration, boolean start) {
|
||||
return setState(flags, duration, 0 /* startDelay */, start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the latest state, potentially calling onStateChangeApplied() and creating a new
|
||||
* animation (stored in mAnimator) which is started if {@param start} is true.
|
||||
* @param flags The latest flags to apply (see the top of this file).
|
||||
* @param duration The length of the animation.
|
||||
* @param startDelay How long to delay the animation after calling start().
|
||||
* @param start Whether to start mAnimator immediately.
|
||||
* @return mAnimator if mIsStashed changed, else null.
|
||||
*/
|
||||
public Animator setState(int flags, long duration, long startDelay, boolean start) {
|
||||
int changedFlags = mPrevFlags ^ flags;
|
||||
if (mPrevFlags != flags) {
|
||||
onStateChangeApplied(changedFlags);
|
||||
mPrevFlags = flags;
|
||||
}
|
||||
boolean isStashed = mStashCondition.test(flags);
|
||||
if (mIsStashed != isStashed) {
|
||||
mIsStashed = isStashed;
|
||||
|
||||
// This sets mAnimator.
|
||||
createAnimToIsStashed(mIsStashed, duration, startDelay);
|
||||
if (start) {
|
||||
mAnimator.start();
|
||||
}
|
||||
return mAnimator;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import static com.android.launcher3.LauncherState.TASKBAR;
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.BaseQuickstepLauncher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.anim.PropertySetter;
|
||||
import com.android.launcher3.statemanager.StateManager;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.quickstep.AnimatedFloat;
|
||||
|
||||
/**
|
||||
* StateHandler to animate Taskbar according to Launcher's state machine. Does nothing if Taskbar
|
||||
* isn't present (i.e. {@link #setAnimationController} is never called).
|
||||
*/
|
||||
public class TaskbarStateHandler implements StateManager.StateHandler<LauncherState> {
|
||||
|
||||
private final BaseQuickstepLauncher mLauncher;
|
||||
|
||||
// Contains Taskbar-related methods and fields we should aniamte. If null, don't do anything.
|
||||
private @Nullable TaskbarAnimationController mAnimationController = null;
|
||||
|
||||
public TaskbarStateHandler(BaseQuickstepLauncher launcher) {
|
||||
mLauncher = launcher;
|
||||
}
|
||||
|
||||
public void setAnimationController(TaskbarAnimationController callbacks) {
|
||||
mAnimationController = callbacks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setState(LauncherState state) {
|
||||
setState(state, PropertySetter.NO_ANIM_PROPERTY_SETTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
|
||||
PendingAnimation animation) {
|
||||
setState(toState, animation);
|
||||
}
|
||||
|
||||
private void setState(LauncherState toState, PropertySetter setter) {
|
||||
if (mAnimationController == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isTaskbarVisible = (toState.getVisibleElements(mLauncher) & TASKBAR) != 0;
|
||||
setter.setFloat(mAnimationController.getTaskbarVisibilityForLauncherState(),
|
||||
AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f, LINEAR);
|
||||
setter.setFloat(mAnimationController.getTaskbarScaleForLauncherState(),
|
||||
AnimatedFloat.VALUE, toState.getTaskbarScale(mLauncher), LINEAR);
|
||||
setter.setFloat(mAnimationController.getTaskbarTranslationYForLauncherState(),
|
||||
AnimatedFloat.VALUE, toState.getTaskbarTranslationY(mLauncher), ACCEL_DEACCEL);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,15 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
|
||||
import com.android.launcher3.model.data.ItemInfoWithIcon;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Base class for providing different taskbar UI
|
||||
*/
|
||||
@@ -22,20 +31,40 @@ public class TaskbarUIController {
|
||||
|
||||
public static final TaskbarUIController DEFAULT = new TaskbarUIController();
|
||||
|
||||
/**
|
||||
* Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat.
|
||||
*/
|
||||
public void alignRealHotseatWithTaskbar() { }
|
||||
// Initialized in init.
|
||||
protected TaskbarControllers mControllers;
|
||||
|
||||
protected void onCreate() { }
|
||||
@CallSuper
|
||||
protected void init(TaskbarControllers taskbarControllers) {
|
||||
mControllers = taskbarControllers;
|
||||
}
|
||||
|
||||
protected void onDestroy() { }
|
||||
@CallSuper
|
||||
protected void onDestroy() {
|
||||
mControllers = null;
|
||||
}
|
||||
|
||||
protected boolean isTaskbarTouchable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void onImeVisible(TaskbarDragLayer container, boolean isVisible) {
|
||||
container.updateImeBarVisibilityAlpha(isVisible ? 1 : 0);
|
||||
protected void onStashedInAppChanged() { }
|
||||
|
||||
public Stream<ItemInfoWithIcon> getAppIconsForEdu() {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
public void onTaskbarIconLaunched(WorkspaceItemInfo item) { }
|
||||
|
||||
public View getRootView() {
|
||||
return mControllers.taskbarActivityContext.getDragLayer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when swiping from the bottom nav region in fully gestural mode.
|
||||
* @param inProgress True if the animation started, false if we just settled on an end target.
|
||||
*/
|
||||
public void setSystemGestureInProgress(boolean inProgress) {
|
||||
mControllers.taskbarStashController.setSystemGestureInProgress(inProgress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.quickstep.util.LauncherViewsMoveFromCenterTranslationApplier;
|
||||
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
|
||||
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
|
||||
|
||||
/**
|
||||
* Controls animation of taskbar icons when unfolding foldable devices
|
||||
*/
|
||||
public class TaskbarUnfoldAnimationController {
|
||||
|
||||
private final ScopedUnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
|
||||
private final UnfoldMoveFromCenterAnimator mMoveFromCenterAnimator;
|
||||
private final TransitionListener mTransitionListener = new TransitionListener();
|
||||
private TaskbarViewController mTaskbarViewController;
|
||||
|
||||
public TaskbarUnfoldAnimationController(ScopedUnfoldTransitionProgressProvider
|
||||
unfoldTransitionProgressProvider, WindowManager windowManager) {
|
||||
mUnfoldTransitionProgressProvider = unfoldTransitionProgressProvider;
|
||||
mMoveFromCenterAnimator = new UnfoldMoveFromCenterAnimator(windowManager,
|
||||
new LauncherViewsMoveFromCenterTranslationApplier());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the controller
|
||||
* @param taskbarControllers references to all other taskbar controllers
|
||||
*/
|
||||
public void init(TaskbarControllers taskbarControllers) {
|
||||
mTaskbarViewController = taskbarControllers.taskbarViewController;
|
||||
mTaskbarViewController.addOneTimePreDrawListener(() ->
|
||||
mUnfoldTransitionProgressProvider.setReadyToHandleTransition(true));
|
||||
mUnfoldTransitionProgressProvider.addCallback(mTransitionListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the controller
|
||||
*/
|
||||
public void onDestroy() {
|
||||
mUnfoldTransitionProgressProvider.setReadyToHandleTransition(false);
|
||||
mUnfoldTransitionProgressProvider.removeCallback(mTransitionListener);
|
||||
}
|
||||
|
||||
private class TransitionListener implements TransitionProgressListener {
|
||||
|
||||
@Override
|
||||
public void onTransitionStarted() {
|
||||
mMoveFromCenterAnimator.updateDisplayProperties();
|
||||
View[] icons = mTaskbarViewController.getIconViews();
|
||||
for (View icon : icons) {
|
||||
// TODO(b/193794563) we should re-register views if they are re-bound/re-inflated
|
||||
// during the animation
|
||||
mMoveFromCenterAnimator.registerViewForAnimation(icon);
|
||||
}
|
||||
|
||||
mMoveFromCenterAnimator.onTransitionStarted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionFinished() {
|
||||
mMoveFromCenterAnimator.onTransitionFinished();
|
||||
mMoveFromCenterAnimator.clearRegisteredViews();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionProgress(float progress) {
|
||||
mMoveFromCenterAnimator.onTransitionProgress(progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,76 +15,54 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static android.view.View.MeasureSpec.EXACTLY;
|
||||
import static android.view.View.MeasureSpec.makeMeasureSpec;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.DragEvent;
|
||||
import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.Insettable;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.folder.FolderIcon;
|
||||
import com.android.launcher3.model.data.FolderInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.uioverrides.ApiWrapper;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
|
||||
/**
|
||||
* Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
|
||||
*/
|
||||
public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent, Insettable {
|
||||
public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable {
|
||||
|
||||
private final int mIconTouchSize;
|
||||
private final boolean mIsRtl;
|
||||
private final int mTouchSlop;
|
||||
private final RectF mTempDelegateBounds = new RectF();
|
||||
private final RectF mDelegateSlopBounds = new RectF();
|
||||
private final int[] mTempOutLocation = new int[2];
|
||||
|
||||
private final Rect mIconLayoutBounds = new Rect();
|
||||
private final int mIconTouchSize;
|
||||
private final int mItemMarginLeftRight;
|
||||
private final int mItemPadding;
|
||||
|
||||
private final TaskbarActivityContext mActivityContext;
|
||||
|
||||
// Initialized in TaskbarController constructor.
|
||||
// Initialized in init.
|
||||
private TaskbarViewController.TaskbarViewCallbacks mControllerCallbacks;
|
||||
private View.OnClickListener mIconClickListener;
|
||||
private View.OnLongClickListener mIconLongClickListener;
|
||||
|
||||
LinearLayout mSystemButtonContainer;
|
||||
LinearLayout mHotseatIconsContainer;
|
||||
|
||||
// Delegate touches to the closest view if within mIconTouchSize.
|
||||
private boolean mDelegateTargeted;
|
||||
private View mDelegateView;
|
||||
// Prevents dispatching touches to children if true
|
||||
private boolean mTouchEnabled = true;
|
||||
|
||||
private boolean mIsDraggingItem;
|
||||
// Only non-null when the corresponding Folder is open.
|
||||
private @Nullable FolderIcon mLeaveBehindFolderIcon;
|
||||
|
||||
/** Provider of buttons added to taskbar in 3 button nav */
|
||||
private ButtonProvider mButtonProvider;
|
||||
|
||||
private boolean mDisableRelayout;
|
||||
private boolean mAreHolesAllowed;
|
||||
|
||||
public TaskbarView(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@@ -105,85 +83,80 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
|
||||
|
||||
Resources resources = getResources();
|
||||
mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size);
|
||||
mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
|
||||
|
||||
mIsRtl = Utilities.isRtl(resources);
|
||||
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
|
||||
int actualIconSize = mActivityContext.getDeviceProfile().iconSizePx;
|
||||
|
||||
// We layout the icons to be of mIconTouchSize in width and height
|
||||
mItemMarginLeftRight = actualMargin - (mIconTouchSize - actualIconSize) / 2;
|
||||
mItemPadding = (mIconTouchSize - actualIconSize) / 2;
|
||||
|
||||
// Needed to draw folder leave-behind when opening one.
|
||||
setWillNotDraw(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mSystemButtonContainer = findViewById(R.id.system_button_layout);
|
||||
mHotseatIconsContainer = findViewById(R.id.hotseat_icons_layout);
|
||||
protected void init(TaskbarViewController.TaskbarViewCallbacks callbacks) {
|
||||
mControllerCallbacks = callbacks;
|
||||
mIconClickListener = mControllerCallbacks.getIconOnClickListener();
|
||||
mIconLongClickListener = mControllerCallbacks.getIconOnLongClickListener();
|
||||
|
||||
setOnLongClickListener(mControllerCallbacks.getBackgroundOnLongClickListener());
|
||||
}
|
||||
|
||||
protected void construct(OnClickListener clickListener, OnLongClickListener longClickListener,
|
||||
ButtonProvider buttonProvider) {
|
||||
mIconClickListener = clickListener;
|
||||
mIconLongClickListener = longClickListener;
|
||||
mButtonProvider = buttonProvider;
|
||||
|
||||
if (mActivityContext.canShowNavButtons()) {
|
||||
createNavButtons();
|
||||
} else {
|
||||
mSystemButtonContainer.setVisibility(GONE);
|
||||
}
|
||||
|
||||
int numHotseatIcons = mActivityContext.getDeviceProfile().numShownHotseatIcons;
|
||||
updateHotseatItems(new ItemInfo[numHotseatIcons]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables/disables empty icons in taskbar so that the layout matches with Launcher
|
||||
*/
|
||||
public void setHolesAllowedInLayout(boolean areHolesAllowed) {
|
||||
if (mAreHolesAllowed != areHolesAllowed) {
|
||||
mAreHolesAllowed = areHolesAllowed;
|
||||
updateHotseatItemsVisibility();
|
||||
// TODO: Add animation
|
||||
}
|
||||
}
|
||||
|
||||
private void setHolesAllowedInLayoutNoAnimation(boolean areHolesAllowed) {
|
||||
if (mAreHolesAllowed != areHolesAllowed) {
|
||||
mAreHolesAllowed = areHolesAllowed;
|
||||
updateHotseatItemsVisibility();
|
||||
onMeasure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
|
||||
makeMeasureSpec(getMeasuredHeight(), EXACTLY));
|
||||
onLayout(false, getLeft(), getTop(), getRight(), getBottom());
|
||||
private void removeAndRecycle(View view) {
|
||||
removeView(view);
|
||||
view.setOnClickListener(null);
|
||||
view.setOnLongClickListener(null);
|
||||
if (!(view.getTag() instanceof FolderInfo)) {
|
||||
mActivityContext.getViewCache().recycleView(view.getSourceLayoutResId(), view);
|
||||
}
|
||||
view.setTag(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates/binds the Hotseat views to show in the Taskbar given their ItemInfos.
|
||||
*/
|
||||
protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
|
||||
int nextViewIndex = 0;
|
||||
int numViewsAnimated = 0;
|
||||
|
||||
for (int i = 0; i < hotseatItemInfos.length; i++) {
|
||||
ItemInfo hotseatItemInfo = hotseatItemInfos[
|
||||
!mIsRtl ? i : hotseatItemInfos.length - i - 1];
|
||||
View hotseatView = mHotseatIconsContainer.getChildAt(i);
|
||||
ItemInfo hotseatItemInfo = hotseatItemInfos[i];
|
||||
if (hotseatItemInfo == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Replace any Hotseat views with the appropriate type if it's not already that type.
|
||||
final int expectedLayoutResId;
|
||||
boolean isFolder = false;
|
||||
boolean needsReinflate = false;
|
||||
if (hotseatItemInfo != null && hotseatItemInfo.isPredictedItem()) {
|
||||
if (hotseatItemInfo.isPredictedItem()) {
|
||||
expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
|
||||
} else if (hotseatItemInfo instanceof FolderInfo) {
|
||||
expectedLayoutResId = R.layout.folder_icon;
|
||||
isFolder = true;
|
||||
// Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation, so
|
||||
// if the info changes we need to reinflate. This should only happen if a new folder
|
||||
// is dragged to the position that another folder previously existed.
|
||||
needsReinflate = hotseatView != null && hotseatView.getTag() != hotseatItemInfo;
|
||||
} else {
|
||||
expectedLayoutResId = R.layout.taskbar_app_icon;
|
||||
}
|
||||
if (hotseatView == null
|
||||
|| hotseatView.getSourceLayoutResId() != expectedLayoutResId
|
||||
|| needsReinflate) {
|
||||
mHotseatIconsContainer.removeView(hotseatView);
|
||||
|
||||
View hotseatView = null;
|
||||
while (nextViewIndex < getChildCount()) {
|
||||
hotseatView = getChildAt(nextViewIndex);
|
||||
|
||||
// see if the view can be reused
|
||||
if ((hotseatView.getSourceLayoutResId() != expectedLayoutResId)
|
||||
|| (isFolder && (hotseatView.getTag() != hotseatItemInfo))) {
|
||||
// Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation,
|
||||
// so if the info changes we need to reinflate. This should only happen if a new
|
||||
// folder is dragged to the position that another folder previously existed.
|
||||
removeAndRecycle(hotseatView);
|
||||
hotseatView = null;
|
||||
} else {
|
||||
// View found
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hotseatView == null) {
|
||||
if (isFolder) {
|
||||
FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
|
||||
FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
|
||||
@@ -193,40 +166,68 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
|
||||
} else {
|
||||
hotseatView = inflate(expectedLayoutResId);
|
||||
}
|
||||
int iconSize = mActivityContext.getDeviceProfile().iconSizePx;
|
||||
LayoutParams lp = new LayoutParams(iconSize, iconSize);
|
||||
lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
|
||||
mHotseatIconsContainer.addView(hotseatView, i, lp);
|
||||
LayoutParams lp = new LayoutParams(mIconTouchSize, mIconTouchSize);
|
||||
hotseatView.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
|
||||
addView(hotseatView, nextViewIndex, lp);
|
||||
}
|
||||
|
||||
// Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
|
||||
if (hotseatView instanceof BubbleTextView
|
||||
&& hotseatItemInfo instanceof WorkspaceItemInfo) {
|
||||
((BubbleTextView) hotseatView).applyFromWorkspaceItem(
|
||||
(WorkspaceItemInfo) hotseatItemInfo);
|
||||
hotseatView.setOnClickListener(mIconClickListener);
|
||||
hotseatView.setOnLongClickListener(mIconLongClickListener);
|
||||
} else if (isFolder) {
|
||||
hotseatView.setOnClickListener(mIconClickListener);
|
||||
hotseatView.setOnLongClickListener(mIconLongClickListener);
|
||||
} else {
|
||||
hotseatView.setOnClickListener(null);
|
||||
hotseatView.setOnLongClickListener(null);
|
||||
hotseatView.setTag(null);
|
||||
BubbleTextView btv = (BubbleTextView) hotseatView;
|
||||
WorkspaceItemInfo workspaceInfo = (WorkspaceItemInfo) hotseatItemInfo;
|
||||
|
||||
boolean animate = btv.shouldAnimateIconChange((WorkspaceItemInfo) hotseatItemInfo);
|
||||
btv.applyFromWorkspaceItem(workspaceInfo, animate, numViewsAnimated);
|
||||
if (animate) {
|
||||
numViewsAnimated++;
|
||||
}
|
||||
}
|
||||
updateHotseatItemVisibility(hotseatView);
|
||||
setClickAndLongClickListenersForIcon(hotseatView);
|
||||
nextViewIndex++;
|
||||
}
|
||||
// Remove remaining views
|
||||
while (nextViewIndex < getChildCount()) {
|
||||
removeAndRecycle(getChildAt(nextViewIndex));
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateHotseatItemsVisibility() {
|
||||
for (int i = mHotseatIconsContainer.getChildCount() - 1; i >= 0; i--) {
|
||||
updateHotseatItemVisibility(mHotseatIconsContainer.getChildAt(i));
|
||||
}
|
||||
/**
|
||||
* Sets OnClickListener and OnLongClickListener for the given view.
|
||||
*/
|
||||
public void setClickAndLongClickListenersForIcon(View icon) {
|
||||
icon.setOnClickListener(mIconClickListener);
|
||||
icon.setOnLongClickListener(mIconLongClickListener);
|
||||
}
|
||||
|
||||
private void updateHotseatItemVisibility(View hotseatView) {
|
||||
hotseatView.setVisibility(
|
||||
hotseatView.getTag() != null ? VISIBLE : (mAreHolesAllowed ? INVISIBLE : GONE));
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
int count = getChildCount();
|
||||
int spaceNeeded = count * (mItemMarginLeftRight * 2 + mIconTouchSize);
|
||||
int navSpaceNeeded = ApiWrapper.getHotseatEndOffset(getContext());
|
||||
boolean layoutRtl = isLayoutRtl();
|
||||
int iconEnd = right - (right - left - spaceNeeded) / 2;
|
||||
boolean needMoreSpaceForNav = layoutRtl ?
|
||||
navSpaceNeeded > (iconEnd - spaceNeeded) :
|
||||
iconEnd > (right - navSpaceNeeded);
|
||||
if (needMoreSpaceForNav) {
|
||||
int offset = layoutRtl ?
|
||||
navSpaceNeeded - (iconEnd - spaceNeeded) :
|
||||
(right - navSpaceNeeded) - iconEnd;
|
||||
iconEnd += offset;
|
||||
}
|
||||
// Layout the children
|
||||
mIconLayoutBounds.right = iconEnd;
|
||||
mIconLayoutBounds.top = (bottom - top - mIconTouchSize) / 2;
|
||||
mIconLayoutBounds.bottom = mIconLayoutBounds.top + mIconTouchSize;
|
||||
for (int i = count; i > 0; i--) {
|
||||
View child = getChildAt(i - 1);
|
||||
iconEnd -= mItemMarginLeftRight;
|
||||
int iconStart = iconEnd - mIconTouchSize;
|
||||
child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
|
||||
iconEnd = iconStart - mItemMarginLeftRight;
|
||||
}
|
||||
mIconLayoutBounds.left = iconEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -239,158 +240,54 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
boolean handled = delegateTouchIfNecessary(event);
|
||||
return super.onTouchEvent(event) || handled;
|
||||
if (!mTouchEnabled) {
|
||||
return true;
|
||||
}
|
||||
if (mIconLayoutBounds.contains((int) event.getX(), (int) event.getY())) {
|
||||
// Don't allow long pressing between icons.
|
||||
return true;
|
||||
}
|
||||
if (mControllerCallbacks.onTouchEvent(event)) {
|
||||
int oldAction = event.getAction();
|
||||
try {
|
||||
event.setAction(MotionEvent.ACTION_CANCEL);
|
||||
return super.onTouchEvent(event);
|
||||
} finally {
|
||||
event.setAction(oldAction);
|
||||
}
|
||||
}
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
public void setTouchesEnabled(boolean touchEnabled) {
|
||||
this.mTouchEnabled = touchEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* User touched the Taskbar background. Determine whether the touch is close enough to a view
|
||||
* that we should forward the touches to it.
|
||||
* @return Whether a delegate view was chosen and it handled the touch event.
|
||||
*/
|
||||
private boolean delegateTouchIfNecessary(MotionEvent event) {
|
||||
final float x = event.getX();
|
||||
final float y = event.getY();
|
||||
if (mDelegateView == null && event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
View delegateView = findDelegateView(x, y);
|
||||
if (delegateView != null) {
|
||||
mDelegateTargeted = true;
|
||||
mDelegateView = delegateView;
|
||||
mDelegateSlopBounds.set(mTempDelegateBounds);
|
||||
mDelegateSlopBounds.inset(-mTouchSlop, -mTouchSlop);
|
||||
}
|
||||
}
|
||||
|
||||
boolean sendToDelegate = mDelegateTargeted;
|
||||
boolean inBounds = true;
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
inBounds = mDelegateSlopBounds.contains(x, y);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
mDelegateTargeted = false;
|
||||
break;
|
||||
}
|
||||
|
||||
boolean handled = false;
|
||||
if (sendToDelegate) {
|
||||
if (inBounds) {
|
||||
// Offset event coordinates to be inside the target view
|
||||
event.setLocation(mDelegateView.getWidth() / 2f, mDelegateView.getHeight() / 2f);
|
||||
} else {
|
||||
// Offset event coordinates to be outside the target view (in case it does
|
||||
// something like tracking pressed state)
|
||||
event.setLocation(-mTouchSlop * 2, -mTouchSlop * 2);
|
||||
}
|
||||
handled = mDelegateView.dispatchTouchEvent(event);
|
||||
// Cleanup if this was the last event to send to the delegate.
|
||||
if (!mDelegateTargeted) {
|
||||
mDelegateView = null;
|
||||
}
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an item whose touch bounds contain the given coordinates,
|
||||
* or null if no such item exists.
|
||||
*
|
||||
* Also sets {@link #mTempDelegateBounds} to be the touch bounds of the chosen delegate view.
|
||||
*/
|
||||
private @Nullable View findDelegateView(float x, float y) {
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View child = getChildAt(i);
|
||||
if (!child.isShown() || !child.isClickable()) {
|
||||
continue;
|
||||
}
|
||||
int childCenterX = child.getLeft() + child.getWidth() / 2;
|
||||
int childCenterY = child.getTop() + child.getHeight() / 2;
|
||||
mTempDelegateBounds.set(
|
||||
childCenterX - mIconTouchSize / 2f,
|
||||
childCenterY - mIconTouchSize / 2f,
|
||||
childCenterX + mIconTouchSize / 2f,
|
||||
childCenterY + mIconTouchSize / 2f);
|
||||
if (mTempDelegateBounds.contains(x, y)) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's
|
||||
* touch bounds.
|
||||
*/
|
||||
public boolean isEventOverAnyItem(MotionEvent ev) {
|
||||
getLocationOnScreen(mTempOutLocation);
|
||||
float xInOurCoordinates = ev.getX() - mTempOutLocation[0];
|
||||
float yInOurCoorindates = ev.getY() - mTempOutLocation[1];
|
||||
return findDelegateView(xInOurCoordinates, yInOurCoorindates) != null;
|
||||
int xInOurCoordinates = (int) ev.getX() - mTempOutLocation[0];
|
||||
int yInOurCoorindates = (int) ev.getY() - mTempOutLocation[1];
|
||||
return isShown() && mIconLayoutBounds.contains(xInOurCoordinates, yInOurCoorindates);
|
||||
}
|
||||
|
||||
public Rect getIconLayoutBounds() {
|
||||
return mIconLayoutBounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add back/home/recents buttons into a single ViewGroup that will be inserted at
|
||||
* {@param navButtonStartIndex}
|
||||
* Returns the app icons currently shown in the taskbar.
|
||||
*/
|
||||
private void createNavButtons() {
|
||||
LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams(
|
||||
mActivityContext.getDeviceProfile().iconSizePx,
|
||||
mActivityContext.getDeviceProfile().iconSizePx
|
||||
);
|
||||
buttonParams.gravity = Gravity.CENTER;
|
||||
|
||||
mSystemButtonContainer.addView(mButtonProvider.getBack(), buttonParams);
|
||||
mSystemButtonContainer.addView(mButtonProvider.getHome(), buttonParams);
|
||||
mSystemButtonContainer.addView(mButtonProvider.getRecents(), buttonParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDragEvent(DragEvent event) {
|
||||
switch (event.getAction()) {
|
||||
case DragEvent.ACTION_DRAG_STARTED:
|
||||
mIsDraggingItem = true;
|
||||
AbstractFloatingView.closeAllOpenViews(mActivityContext);
|
||||
return true;
|
||||
case DragEvent.ACTION_DRAG_ENDED:
|
||||
mIsDraggingItem = false;
|
||||
break;
|
||||
}
|
||||
return super.onDragEvent(event);
|
||||
}
|
||||
|
||||
public boolean isDraggingItem() {
|
||||
return mIsDraggingItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The bounding box of where the hotseat elements are relative to this TaskbarView.
|
||||
*/
|
||||
protected RectF getHotseatBounds() {
|
||||
RectF result;
|
||||
mDisableRelayout = true;
|
||||
boolean wereHolesAllowed = mAreHolesAllowed;
|
||||
setHolesAllowedInLayoutNoAnimation(true);
|
||||
result = new RectF(
|
||||
mHotseatIconsContainer.getLeft(),
|
||||
mHotseatIconsContainer.getTop(),
|
||||
mHotseatIconsContainer.getRight(),
|
||||
mHotseatIconsContainer.getBottom());
|
||||
setHolesAllowedInLayoutNoAnimation(wereHolesAllowed);
|
||||
mDisableRelayout = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestLayout() {
|
||||
if (!mDisableRelayout) {
|
||||
super.requestLayout();
|
||||
public View[] getIconViews() {
|
||||
final int count = getChildCount();
|
||||
View[] icons = new View[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
icons[i] = getChildAt(i);
|
||||
}
|
||||
return icons;
|
||||
}
|
||||
|
||||
// FolderIconParent implemented methods.
|
||||
@@ -421,7 +318,7 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
|
||||
}
|
||||
|
||||
private View inflate(@LayoutRes int layoutResId) {
|
||||
return mActivityContext.getLayoutInflater().inflate(layoutResId, this, false);
|
||||
return mActivityContext.getViewCache().getView(layoutResId, mActivityContext, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -429,11 +326,8 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
|
||||
// Ignore, we just implement Insettable to draw behind system insets.
|
||||
}
|
||||
|
||||
public void setIconsVisibility(boolean isVisible) {
|
||||
mHotseatIconsContainer.setVisibility(isVisible ? VISIBLE : INVISIBLE);
|
||||
}
|
||||
|
||||
public boolean areIconsVisible() {
|
||||
return mHotseatIconsContainer.getVisibility() == VISIBLE;
|
||||
// Consider the overall visibility
|
||||
return getVisibility() == VISIBLE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.taskbar;
|
||||
|
||||
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
|
||||
import static com.android.launcher3.Utilities.squaredHypot;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.quickstep.AnimatedFloat.VALUE;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.util.FloatProperty;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.ViewTreeObserver.OnPreDrawListener;
|
||||
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.folder.FolderIcon;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.quickstep.AnimatedFloat;
|
||||
|
||||
/**
|
||||
* Handles properties/data collection, then passes the results to TaskbarView to render.
|
||||
*/
|
||||
public class TaskbarViewController {
|
||||
private static final Runnable NO_OP = () -> { };
|
||||
|
||||
public static final int ALPHA_INDEX_HOME = 0;
|
||||
public static final int ALPHA_INDEX_KEYGUARD = 1;
|
||||
public static final int ALPHA_INDEX_STASH = 2;
|
||||
public static final int ALPHA_INDEX_RECENTS_DISABLED = 3;
|
||||
public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 4;
|
||||
private static final int NUM_ALPHA_CHANNELS = 5;
|
||||
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final TaskbarView mTaskbarView;
|
||||
private final MultiValueAlpha mTaskbarIconAlpha;
|
||||
private final AnimatedFloat mTaskbarIconScaleForStash = new AnimatedFloat(this::updateScale);
|
||||
private final AnimatedFloat mTaskbarIconTranslationYForHome = new AnimatedFloat(
|
||||
this::updateTranslationY);
|
||||
private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat(
|
||||
this::updateTranslationY);
|
||||
private AnimatedFloat mTaskbarNavButtonTranslationY;
|
||||
|
||||
private final TaskbarModelCallbacks mModelCallbacks;
|
||||
|
||||
// Initialized in init.
|
||||
private TaskbarControllers mControllers;
|
||||
|
||||
// Animation to align icons with Launcher, created lazily. This allows the controller to be
|
||||
// active only during the animation and does not need to worry about layout changes.
|
||||
private AnimatorPlaybackController mIconAlignControllerLazy = null;
|
||||
private Runnable mOnControllerPreCreateCallback = NO_OP;
|
||||
|
||||
public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) {
|
||||
mActivity = activity;
|
||||
mTaskbarView = taskbarView;
|
||||
mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, NUM_ALPHA_CHANNELS);
|
||||
mTaskbarIconAlpha.setUpdateVisibility(true);
|
||||
mModelCallbacks = new TaskbarModelCallbacks(activity, mTaskbarView);
|
||||
}
|
||||
|
||||
public void init(TaskbarControllers controllers) {
|
||||
mControllers = controllers;
|
||||
mTaskbarView.init(new TaskbarViewCallbacks());
|
||||
mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
|
||||
|
||||
mTaskbarIconScaleForStash.updateValue(1f);
|
||||
|
||||
mModelCallbacks.init(controllers);
|
||||
if (mActivity.isUserSetupComplete()) {
|
||||
// Only load the callbacks if user setup is completed
|
||||
LauncherAppState.getInstance(mActivity).getModel().addCallbacksAndLoad(mModelCallbacks);
|
||||
}
|
||||
mTaskbarNavButtonTranslationY =
|
||||
controllers.navbarButtonsViewController.getTaskbarNavButtonTranslationY();
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
|
||||
}
|
||||
|
||||
public boolean areIconsVisible() {
|
||||
return mTaskbarView.areIconsVisible();
|
||||
}
|
||||
|
||||
public MultiValueAlpha getTaskbarIconAlpha() {
|
||||
return mTaskbarIconAlpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when the IME visibility changes, so we can make Taskbar not steal touches.
|
||||
*/
|
||||
public void setImeIsVisible(boolean isImeVisible) {
|
||||
mTaskbarView.setTouchesEnabled(!isImeVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when the recents button is disabled, so we can hide taskbar icons as well.
|
||||
*/
|
||||
public void setRecentsButtonDisabled(boolean isDisabled) {
|
||||
// TODO: check TaskbarStashController#supportsStashing(), to stash instead of setting alpha.
|
||||
mTaskbarIconAlpha.getProperty(ALPHA_INDEX_RECENTS_DISABLED).setValue(isDisabled ? 0 : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets OnClickListener and OnLongClickListener for the given view.
|
||||
*/
|
||||
public void setClickAndLongClickListenersForIcon(View icon) {
|
||||
mTaskbarView.setClickAndLongClickListenersForIcon(icon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one time pre draw listener to the taskbar view, it is called before
|
||||
* drawing a frame and invoked only once
|
||||
* @param listener callback that will be invoked before drawing the next frame
|
||||
*/
|
||||
public void addOneTimePreDrawListener(Runnable listener) {
|
||||
mTaskbarView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
final ViewTreeObserver viewTreeObserver = mTaskbarView.getViewTreeObserver();
|
||||
if (viewTreeObserver.isAlive()) {
|
||||
listener.run();
|
||||
viewTreeObserver.removeOnPreDrawListener(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Rect getIconLayoutBounds() {
|
||||
return mTaskbarView.getIconLayoutBounds();
|
||||
}
|
||||
|
||||
public View[] getIconViews() {
|
||||
return mTaskbarView.getIconViews();
|
||||
}
|
||||
|
||||
public AnimatedFloat getTaskbarIconScaleForStash() {
|
||||
return mTaskbarIconScaleForStash;
|
||||
}
|
||||
|
||||
public AnimatedFloat getTaskbarIconTranslationYForStash() {
|
||||
return mTaskbarIconTranslationYForStash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies scale properties for the entire TaskbarView (rather than individual icons).
|
||||
*/
|
||||
private void updateScale() {
|
||||
float scale = mTaskbarIconScaleForStash.value;
|
||||
mTaskbarView.setScaleX(scale);
|
||||
mTaskbarView.setScaleY(scale);
|
||||
}
|
||||
|
||||
private void updateTranslationY() {
|
||||
mTaskbarView.setTranslationY(mTaskbarIconTranslationYForHome.value
|
||||
+ mTaskbarIconTranslationYForStash.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the taskbar icon alignment relative to Launcher hotseat icons
|
||||
* @param alignmentRatio [0, 1]
|
||||
* 0 => not aligned
|
||||
* 1 => fully aligned
|
||||
*/
|
||||
public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
|
||||
if (mIconAlignControllerLazy == null) {
|
||||
mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
|
||||
}
|
||||
mIconAlignControllerLazy.setPlayFraction(alignmentRatio);
|
||||
if (alignmentRatio <= 0 || alignmentRatio >= 1) {
|
||||
// Cleanup lazy controller so that it is created again in next animation
|
||||
mIconAlignControllerLazy = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an animation for aligning the taskbar icons with the provided Launcher device profile
|
||||
*/
|
||||
private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
|
||||
mOnControllerPreCreateCallback.run();
|
||||
PendingAnimation setter = new PendingAnimation(100);
|
||||
Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
|
||||
float scaleUp = ((float) launcherDp.iconSizePx) / mActivity.getDeviceProfile().iconSizePx;
|
||||
int hotseatCellSize =
|
||||
(launcherDp.availableWidthPx - hotseatPadding.left - hotseatPadding.right)
|
||||
/ launcherDp.numShownHotseatIcons;
|
||||
|
||||
int offsetY = launcherDp.getTaskbarOffsetY();
|
||||
setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, LINEAR);
|
||||
setter.setFloat(mTaskbarNavButtonTranslationY, VALUE, -offsetY, LINEAR);
|
||||
|
||||
int collapsedHeight = mActivity.getDefaultTaskbarWindowHeight();
|
||||
int expandedHeight = Math.max(collapsedHeight,
|
||||
mActivity.getDeviceProfile().taskbarSize + offsetY);
|
||||
setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight(
|
||||
anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight));
|
||||
|
||||
int count = mTaskbarView.getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
View child = mTaskbarView.getChildAt(i);
|
||||
ItemInfo info = (ItemInfo) child.getTag();
|
||||
setter.setFloat(child, SCALE_PROPERTY, scaleUp, LINEAR);
|
||||
|
||||
float childCenter = (child.getLeft() + child.getRight()) / 2;
|
||||
float hotseatIconCenter = hotseatPadding.left + hotseatCellSize * info.screenId
|
||||
+ hotseatCellSize / 2;
|
||||
setter.setFloat(child, ICON_TRANSLATE_X, hotseatIconCenter - childCenter, LINEAR);
|
||||
}
|
||||
|
||||
AnimatorPlaybackController controller = setter.createPlaybackController();
|
||||
mOnControllerPreCreateCallback = () -> controller.setPlayFraction(0);
|
||||
return controller;
|
||||
}
|
||||
|
||||
public void onRotationChanged(DeviceProfile deviceProfile) {
|
||||
if (areIconsVisible()) {
|
||||
// We only translate on rotation when on home
|
||||
return;
|
||||
}
|
||||
mTaskbarNavButtonTranslationY.updateValue(-deviceProfile.getTaskbarOffsetY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's
|
||||
* touch bounds.
|
||||
*/
|
||||
public boolean isEventOverAnyItem(MotionEvent ev) {
|
||||
return mTaskbarView.isEventOverAnyItem(ev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callbacks for {@link TaskbarView} to interact with its controller.
|
||||
*/
|
||||
public class TaskbarViewCallbacks {
|
||||
private final float mSquaredTouchSlop = Utilities.squaredTouchSlop(mActivity);
|
||||
|
||||
private float mDownX, mDownY;
|
||||
private boolean mCanceledStashHint;
|
||||
|
||||
public View.OnClickListener getIconOnClickListener() {
|
||||
return mActivity.getItemOnClickListener();
|
||||
}
|
||||
|
||||
public View.OnLongClickListener getIconOnLongClickListener() {
|
||||
return mControllers.taskbarDragController::startDragOnLongClick;
|
||||
}
|
||||
|
||||
public View.OnLongClickListener getBackgroundOnLongClickListener() {
|
||||
return view -> mControllers.taskbarStashController
|
||||
.updateAndAnimateIsManuallyStashedInApp(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first chance to handle TaskbarView#onTouchEvent, and return whether we want to
|
||||
* consume the touch so TaskbarView treats it as an ACTION_CANCEL.
|
||||
*/
|
||||
public boolean onTouchEvent(MotionEvent motionEvent) {
|
||||
final float x = motionEvent.getRawX();
|
||||
final float y = motionEvent.getRawY();
|
||||
switch (motionEvent.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mDownX = x;
|
||||
mDownY = y;
|
||||
mControllers.taskbarStashController.startStashHint(/* animateForward = */ true);
|
||||
mCanceledStashHint = false;
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (!mCanceledStashHint
|
||||
&& squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop) {
|
||||
mControllers.taskbarStashController.startStashHint(
|
||||
/* animateForward= */ false);
|
||||
mCanceledStashHint = true;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
if (!mCanceledStashHint) {
|
||||
mControllers.taskbarStashController.startStashHint(
|
||||
/* animateForward= */ false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static final FloatProperty<View> ICON_TRANSLATE_X =
|
||||
new FloatProperty<View>("taskbarAligmentTranslateX") {
|
||||
|
||||
@Override
|
||||
public void setValue(View view, float v) {
|
||||
if (view instanceof BubbleTextView) {
|
||||
((BubbleTextView) view).setTranslationXForTaskbarAlignmentAnimation(v);
|
||||
} else if (view instanceof FolderIcon) {
|
||||
((FolderIcon) view).setTranslationForTaskbarAlignmentAnimation(v);
|
||||
} else {
|
||||
view.setTranslationX(v);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(View view) {
|
||||
if (view instanceof BubbleTextView) {
|
||||
return ((BubbleTextView) view)
|
||||
.getTranslationXForTaskbarAlignmentAnimation();
|
||||
} else if (view instanceof FolderIcon) {
|
||||
return ((FolderIcon) view).getTranslationXForTaskbarAlignmentAnimation();
|
||||
}
|
||||
return view.getTranslationX();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -17,10 +17,15 @@
|
||||
package com.android.launcher3.uioverrides;
|
||||
|
||||
import android.app.Person;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.view.Display;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.quickstep.SysUINavigationMode;
|
||||
import com.android.quickstep.SysUINavigationMode.Mode;
|
||||
|
||||
public class ApiWrapper {
|
||||
|
||||
@@ -38,4 +43,31 @@ public class ApiWrapper {
|
||||
public static boolean isInternalDisplay(Display display) {
|
||||
return display.getType() == Display.TYPE_INTERNAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a unique ID representing the display
|
||||
*/
|
||||
public static String getUniqueId(Display display) {
|
||||
return display.getUniqueId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum space that should be left empty at the end of hotseat
|
||||
*/
|
||||
public static int getHotseatEndOffset(Context context) {
|
||||
if (SysUINavigationMode.INSTANCE.get(context).getMode() == Mode.THREE_BUTTONS) {
|
||||
Resources res = context.getResources();
|
||||
/*
|
||||
* 3 nav buttons +
|
||||
* Little space at the end for contextual buttons +
|
||||
* Little space between icons and nav buttons
|
||||
*/
|
||||
return 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
|
||||
+ res.getDimensionPixelSize(R.dimen.taskbar_contextual_button_margin)
|
||||
+ res.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+5
-12
@@ -17,6 +17,8 @@
|
||||
package com.android.launcher3.uioverrides;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
|
||||
import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
|
||||
import static com.android.launcher3.anim.Interpolators.INSTANT;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
|
||||
@@ -27,8 +29,6 @@ import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
|
||||
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
|
||||
import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
|
||||
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
|
||||
import static com.android.quickstep.views.RecentsView.TASK_PRIMARY_SPLIT_TRANSLATION;
|
||||
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION;
|
||||
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
|
||||
|
||||
import android.util.FloatProperty;
|
||||
@@ -40,7 +40,6 @@ import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.statemanager.StateManager.StateHandler;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.launcher3.touch.PagedOrientationHandler;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
|
||||
/**
|
||||
@@ -97,13 +96,6 @@ public abstract class BaseRecentsViewStateController<T extends RecentsView>
|
||||
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
|
||||
setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
|
||||
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
|
||||
PagedOrientationHandler orientationHandler =
|
||||
((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
|
||||
FloatProperty taskViewsFloat = orientationHandler.getSplitSelectTaskOffset(
|
||||
TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
|
||||
mLauncher.getDeviceProfile());
|
||||
setter.setFloat(mRecentsView, taskViewsFloat,
|
||||
toState.getSplitSelectTranslation(mLauncher), LINEAR);
|
||||
|
||||
setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
|
||||
config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
|
||||
@@ -112,8 +104,9 @@ public abstract class BaseRecentsViewStateController<T extends RecentsView>
|
||||
mRecentsView, getTaskModalnessProperty(),
|
||||
toState.getOverviewModalness(),
|
||||
config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
|
||||
setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
|
||||
toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f, LINEAR);
|
||||
boolean showAsGrid = toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile());
|
||||
setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, showAsGrid ? 1f : 0f,
|
||||
showAsGrid ? INSTANT : FINAL_FRAME);
|
||||
}
|
||||
|
||||
abstract FloatProperty getTaskModalnessProperty();
|
||||
|
||||
@@ -15,6 +15,16 @@
|
||||
*/
|
||||
package com.android.launcher3.uioverrides;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.Keyframe;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.graphics.BlurMaskFilter;
|
||||
import android.graphics.Canvas;
|
||||
@@ -23,8 +33,10 @@ import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Process;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.FloatProperty;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
@@ -35,6 +47,8 @@ import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.launcher3.icons.GraphicsUtils;
|
||||
import com.android.launcher3.icons.IconNormalizer;
|
||||
import com.android.launcher3.icons.LauncherIcons;
|
||||
@@ -45,6 +59,10 @@ import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
import com.android.launcher3.views.DoubleShadowBubbleTextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A BubbleTextView with a ring around it's drawable
|
||||
*/
|
||||
@@ -53,6 +71,9 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
private static final int RING_SHADOW_COLOR = 0x99000000;
|
||||
private static final float RING_EFFECT_RATIO = 0.095f;
|
||||
|
||||
private static final long ICON_CHANGE_ANIM_DURATION = 360;
|
||||
private static final long ICON_CHANGE_ANIM_STAGGER = 50;
|
||||
|
||||
boolean mIsDrawingDot = false;
|
||||
private final DeviceProfile mDeviceProfile;
|
||||
private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
@@ -67,6 +88,25 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
private int mPlateColor;
|
||||
boolean mDrawForDrag = false;
|
||||
|
||||
// Used for the "slot-machine" education animation.
|
||||
private List<Drawable> mSlotMachineIcons;
|
||||
private Animator mSlotMachineAnim;
|
||||
private float mSlotMachineIconTranslationY;
|
||||
|
||||
private static final FloatProperty<PredictedAppIcon> SLOT_MACHINE_TRANSLATION_Y =
|
||||
new FloatProperty<PredictedAppIcon>("slotMachineTranslationY") {
|
||||
@Override
|
||||
public void setValue(PredictedAppIcon predictedAppIcon, float transY) {
|
||||
predictedAppIcon.mSlotMachineIconTranslationY = transY;
|
||||
predictedAppIcon.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(PredictedAppIcon predictedAppIcon) {
|
||||
return predictedAppIcon.mSlotMachineIconTranslationY;
|
||||
}
|
||||
};
|
||||
|
||||
public PredictedAppIcon(Context context) {
|
||||
this(context, null, 0);
|
||||
}
|
||||
@@ -88,15 +128,38 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
int count = canvas.save();
|
||||
boolean isSlotMachineAnimRunning = mSlotMachineAnim != null;
|
||||
if (!mIsPinned) {
|
||||
drawEffect(canvas);
|
||||
if (isSlotMachineAnimRunning) {
|
||||
// Clip to to outside of the ring during the slot machine animation.
|
||||
canvas.clipPath(mRingPath);
|
||||
}
|
||||
canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
|
||||
canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
|
||||
}
|
||||
super.onDraw(canvas);
|
||||
if (isSlotMachineAnimRunning) {
|
||||
drawSlotMachineIcons(canvas);
|
||||
} else {
|
||||
super.onDraw(canvas);
|
||||
}
|
||||
canvas.restoreToCount(count);
|
||||
}
|
||||
|
||||
private void drawSlotMachineIcons(Canvas canvas) {
|
||||
canvas.translate((getWidth() - getIconSize()) / 2f,
|
||||
(getHeight() - getIconSize()) / 2f + mSlotMachineIconTranslationY);
|
||||
for (Drawable icon : mSlotMachineIcons) {
|
||||
icon.setBounds(0, 0, getIconSize(), getIconSize());
|
||||
icon.draw(canvas);
|
||||
canvas.translate(0, getSlotMachineIconPlusSpacingSize());
|
||||
}
|
||||
}
|
||||
|
||||
private float getSlotMachineIconPlusSpacingSize() {
|
||||
return getIconSize() + getOutlineOffsetY();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawDotIfNecessary(Canvas canvas) {
|
||||
mIsDrawingDot = true;
|
||||
@@ -109,9 +172,17 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
|
||||
super.applyFromWorkspaceItem(info);
|
||||
mPlateColor = ColorUtils.setAlphaComponent(mDotParams.color, 200);
|
||||
public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
|
||||
// Create the slot machine animation first, since it uses the current icon to start.
|
||||
Animator slotMachineAnim = animate
|
||||
? createSlotMachineAnim(Collections.singletonList(info.bitmap), false)
|
||||
: null;
|
||||
super.applyFromWorkspaceItem(info, animate, staggerIndex);
|
||||
int oldPlateColor = mPlateColor;
|
||||
int newPlateColor = ColorUtils.setAlphaComponent(mDotParams.color, 200);
|
||||
if (!animate) {
|
||||
mPlateColor = newPlateColor;
|
||||
}
|
||||
if (mIsPinned) {
|
||||
setContentDescription(info.contentDescription);
|
||||
} else {
|
||||
@@ -119,6 +190,76 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
getContext().getString(R.string.hotseat_prediction_content_description,
|
||||
info.contentDescription));
|
||||
}
|
||||
|
||||
if (animate) {
|
||||
ValueAnimator plateColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(),
|
||||
oldPlateColor, newPlateColor);
|
||||
plateColorAnim.addUpdateListener(valueAnimator -> {
|
||||
mPlateColor = (int) valueAnimator.getAnimatedValue();
|
||||
invalidate();
|
||||
});
|
||||
AnimatorSet changeIconAnim = new AnimatorSet();
|
||||
if (slotMachineAnim != null) {
|
||||
changeIconAnim.play(slotMachineAnim);
|
||||
}
|
||||
changeIconAnim.play(plateColorAnim);
|
||||
changeIconAnim.setStartDelay(staggerIndex * ICON_CHANGE_ANIM_STAGGER);
|
||||
changeIconAnim.setDuration(ICON_CHANGE_ANIM_DURATION).start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
|
||||
* and ending with the original icon.
|
||||
*/
|
||||
public @Nullable Animator createSlotMachineAnim(List<BitmapInfo> iconsToAnimate) {
|
||||
return createSlotMachineAnim(iconsToAnimate, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
|
||||
* with the original icon, then cycling through the given icons, optionally ending back with
|
||||
* the original icon.
|
||||
* @param endWithOriginalIcon Whether we should land back on the icon we started with, rather
|
||||
* than the last item in iconsToAnimate.
|
||||
*/
|
||||
public @Nullable Animator createSlotMachineAnim(List<BitmapInfo> iconsToAnimate,
|
||||
boolean endWithOriginalIcon) {
|
||||
if (mIsPinned || iconsToAnimate == null || iconsToAnimate.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (mSlotMachineAnim != null) {
|
||||
mSlotMachineAnim.end();
|
||||
}
|
||||
|
||||
// Bookend the other animating icons with the original icon on both ends.
|
||||
mSlotMachineIcons = new ArrayList<>(iconsToAnimate.size() + 2);
|
||||
mSlotMachineIcons.add(getIcon());
|
||||
iconsToAnimate.stream()
|
||||
.map(iconInfo -> iconInfo.newThemedIcon(mContext))
|
||||
.forEach(mSlotMachineIcons::add);
|
||||
if (endWithOriginalIcon) {
|
||||
mSlotMachineIcons.add(getIcon());
|
||||
}
|
||||
|
||||
float finalTrans = -getSlotMachineIconPlusSpacingSize() * (mSlotMachineIcons.size() - 1);
|
||||
Keyframe[] keyframes = new Keyframe[] {
|
||||
Keyframe.ofFloat(0f, 0f),
|
||||
Keyframe.ofFloat(0.82f, finalTrans - getOutlineOffsetY() / 2f), // Overshoot
|
||||
Keyframe.ofFloat(1f, finalTrans) // Ease back into the final position
|
||||
};
|
||||
keyframes[1].setInterpolator(ACCEL_DEACCEL);
|
||||
keyframes[2].setInterpolator(ACCEL_DEACCEL);
|
||||
|
||||
mSlotMachineAnim = ObjectAnimator.ofPropertyValuesHolder(this,
|
||||
PropertyValuesHolder.ofKeyframe(SLOT_MACHINE_TRANSLATION_Y, keyframes));
|
||||
mSlotMachineAnim.addListener(AnimatorListeners.forEndCallback(() -> {
|
||||
mSlotMachineIcons = null;
|
||||
mSlotMachineAnim = null;
|
||||
mSlotMachineIconTranslationY = 0;
|
||||
invalidate();
|
||||
}));
|
||||
return mSlotMachineAnim;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -71,7 +71,7 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
|
||||
}
|
||||
}
|
||||
activityOptions.options.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
activityOptions.options.setSplashscreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
|
||||
activityOptions.options.setSplashscreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_EMPTY);
|
||||
Object itemInfo = hostView.getTag();
|
||||
if (itemInfo instanceof ItemInfo) {
|
||||
mLauncher.addLaunchCookie((ItemInfo) itemInfo, activityOptions.options);
|
||||
|
||||
@@ -52,10 +52,10 @@ import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.appprediction.PredictionRowView;
|
||||
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
|
||||
import com.android.launcher3.logging.InstanceId;
|
||||
import com.android.launcher3.logging.StatsLogManager;
|
||||
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
|
||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
|
||||
import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
|
||||
@@ -68,7 +68,9 @@ import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchControll
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
|
||||
import com.android.launcher3.util.ItemInfoMatcher;
|
||||
import com.android.launcher3.util.OnboardingPrefs;
|
||||
import com.android.launcher3.util.PendingRequestArgs;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.launcher3.util.UiThreadHelper;
|
||||
import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
|
||||
@@ -84,7 +86,6 @@ import com.android.quickstep.views.TaskView;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -109,7 +110,8 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
|
||||
public void logAppLaunch(StatsLogManager statsLogManager, ItemInfo info,
|
||||
InstanceId instanceId) {
|
||||
// If the app launch is from any of the surfaces in AllApps then add the InstanceId from
|
||||
// LiveSearchManager to recreate the AllApps session on the server side.
|
||||
if (mAllAppsSessionLogId != null && ALL_APPS.equals(
|
||||
@@ -117,8 +119,7 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
|
||||
instanceId = mAllAppsSessionLogId;
|
||||
}
|
||||
|
||||
StatsLogger logger = getStatsLogManager()
|
||||
.logger().withItemInfo(info).withInstanceId(instanceId);
|
||||
StatsLogger logger = statsLogManager.logger().withItemInfo(info).withInstanceId(instanceId);
|
||||
|
||||
if (mAllAppsPredictions != null
|
||||
&& (info.itemType == ITEM_TYPE_APPLICATION
|
||||
@@ -141,6 +142,15 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
|
||||
mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void completeAddShortcut(Intent data, int container, int screenId, int cellX,
|
||||
int cellY, PendingRequestArgs args) {
|
||||
if (container == CONTAINER_HOTSEAT) {
|
||||
mHotseatPredictionController.onDeferredDrop(cellX, cellY);
|
||||
}
|
||||
super.completeAddShortcut(data, container, screenId, cellX, cellY, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LauncherAccessibilityDelegate createAccessibilityDelegate() {
|
||||
return new QuickstepAccessibilityDelegate(this);
|
||||
@@ -168,7 +178,11 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
|
||||
public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
|
||||
// Only pause is taskbar controller is not present
|
||||
mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);
|
||||
return super.startActivitySafely(v, intent, item);
|
||||
boolean started = super.startActivitySafely(v, intent, item);
|
||||
if (getTaskbarUIController() == null && !started) {
|
||||
mHotseatPredictionController.setPauseUIUpdate(false);
|
||||
}
|
||||
return started;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -233,12 +247,9 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
|
||||
super.bindWorkspaceItemsChanged(updated);
|
||||
if (getTaskbarUIController() != null && updated.stream()
|
||||
.filter(w -> w.container == CONTAINER_HOTSEAT).findFirst().isPresent()) {
|
||||
getTaskbarUIController().onHotseatUpdated();
|
||||
}
|
||||
public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) {
|
||||
super.bindWorkspaceComponentsRemoved(matcher);
|
||||
mHotseatPredictionController.onModelItemsRemoved(matcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,20 +18,22 @@ package com.android.launcher3.uioverrides;
|
||||
import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
|
||||
import static com.android.launcher3.LauncherState.SPLIT_PLACHOLDER_VIEW;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
|
||||
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
|
||||
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
|
||||
import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
|
||||
import static com.android.quickstep.views.SplitPlaceholderView.ALPHA_FLOAT;
|
||||
import static com.android.quickstep.views.RecentsView.TASK_PRIMARY_SPLIT_TRANSLATION;
|
||||
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION;
|
||||
import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.util.FloatProperty;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.BaseQuickstepLauncher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
@@ -39,6 +41,7 @@ import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.anim.PropertySetter;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.launcher3.touch.PagedOrientationHandler;
|
||||
import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.quickstep.views.ClearAllButton;
|
||||
import com.android.quickstep.views.LauncherRecentsView;
|
||||
@@ -65,6 +68,11 @@ public final class RecentsViewStateController extends
|
||||
}
|
||||
setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, new StateAnimationConfig(), state);
|
||||
mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress());
|
||||
// In Overview, we may be layering app surfaces behind Launcher, so we need to notify
|
||||
// DepthController to prevent optimizations which might occlude the layers behind
|
||||
mLauncher.getDepthController().setHasContentBehindLauncher(state.overviewUi);
|
||||
|
||||
handleSplitSelectionState(state, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,14 +88,12 @@ public final class RecentsViewStateController extends
|
||||
builder.addListener(
|
||||
AnimatorListeners.forSuccessCallback(mRecentsView::resetTaskVisuals));
|
||||
}
|
||||
// In Overview, we may be layering app surfaces behind Launcher, so we need to notify
|
||||
// DepthController to prevent optimizations which might occlude the layers behind
|
||||
builder.addListener(AnimatorListeners.forSuccessCallback(() ->
|
||||
mLauncher.getDepthController().setHasContentBehindLauncher(toState.overviewUi)));
|
||||
|
||||
// Create or dismiss split screen select animations
|
||||
LauncherState currentState = mLauncher.getStateManager().getState();
|
||||
if (isSplitSelectionState(toState) && !isSplitSelectionState(currentState)) {
|
||||
builder.add(mRecentsView.createSplitSelectInitAnimation().buildAnim());
|
||||
} else if (!isSplitSelectionState(toState) && isSplitSelectionState(currentState)) {
|
||||
builder.add(mRecentsView.cancelSplitSelect(true).buildAnim());
|
||||
}
|
||||
handleSplitSelectionState(toState, builder);
|
||||
|
||||
setAlphas(builder, config, toState);
|
||||
builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
|
||||
@@ -95,10 +101,52 @@ public final class RecentsViewStateController extends
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if {@param toState} is {@link LauncherState#OVERVIEW_SPLIT_SELECT}
|
||||
* Create or dismiss split screen select animations.
|
||||
* @param builder if null then this will run the split select animations right away, otherwise
|
||||
* will add animations to builder.
|
||||
*/
|
||||
private boolean isSplitSelectionState(@NonNull LauncherState toState) {
|
||||
return toState == OVERVIEW_SPLIT_SELECT;
|
||||
private void handleSplitSelectionState(@NonNull LauncherState toState,
|
||||
@Nullable PendingAnimation builder) {
|
||||
LauncherState currentState = mLauncher.getStateManager().getState();
|
||||
boolean animate = builder != null;
|
||||
PagedOrientationHandler orientationHandler =
|
||||
((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
|
||||
Pair<FloatProperty, FloatProperty> taskViewsFloat =
|
||||
orientationHandler.getSplitSelectTaskOffset(
|
||||
TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
|
||||
mLauncher.getDeviceProfile());
|
||||
|
||||
if (isSplitSelectionState(currentState, toState)) {
|
||||
// Animation to "dismiss" selected taskView
|
||||
PendingAnimation splitSelectInitAnimation =
|
||||
mRecentsView.createSplitSelectInitAnimation();
|
||||
// Add properties to shift remaining taskViews to get out of placeholder view
|
||||
splitSelectInitAnimation.setFloat(mRecentsView, taskViewsFloat.first,
|
||||
toState.getSplitSelectTranslation(mLauncher), LINEAR);
|
||||
splitSelectInitAnimation.setFloat(mRecentsView, taskViewsFloat.second, 0, LINEAR);
|
||||
|
||||
if (!animate && isSplitSelectionState(currentState, toState)) {
|
||||
splitSelectInitAnimation.buildAnim().start();
|
||||
} else if (animate &&
|
||||
isSplitSelectionState(currentState, toState)) {
|
||||
builder.add(splitSelectInitAnimation.buildAnim());
|
||||
}
|
||||
}
|
||||
|
||||
if (isSplitSelectionState(currentState, toState)) {
|
||||
mRecentsView.applySplitPrimaryScrollOffset();
|
||||
} else {
|
||||
mRecentsView.resetSplitPrimaryScrollOffset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if {@param toState} is {@link LauncherState#OVERVIEW_SPLIT_SELECT}
|
||||
* and {@param fromState} is not {@link LauncherState#OVERVIEW_SPLIT_SELECT}
|
||||
*/
|
||||
private boolean isSplitSelectionState(@NonNull LauncherState fromState,
|
||||
@NonNull LauncherState toState) {
|
||||
return fromState != OVERVIEW_SPLIT_SELECT && toState == OVERVIEW_SPLIT_SELECT;
|
||||
}
|
||||
|
||||
private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config,
|
||||
@@ -106,16 +154,10 @@ public final class RecentsViewStateController extends
|
||||
float clearAllButtonAlpha = state.areElementsVisible(mLauncher, CLEAR_ALL_BUTTON) ? 1 : 0;
|
||||
propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
|
||||
clearAllButtonAlpha, LINEAR);
|
||||
float overviewButtonAlpha = state.areElementsVisible(mLauncher, OVERVIEW_ACTIONS)
|
||||
&& mRecentsView.shouldShowOverviewActionsForState(state) ? 1 : 0;
|
||||
float overviewButtonAlpha = state.areElementsVisible(mLauncher, OVERVIEW_ACTIONS) ? 1 : 0;
|
||||
propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
|
||||
MultiValueAlpha.VALUE, overviewButtonAlpha, config.getInterpolator(
|
||||
ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
|
||||
|
||||
float splitPlaceholderAlpha = state.areElementsVisible(mLauncher, SPLIT_PLACHOLDER_VIEW) ?
|
||||
0.85f : 0;
|
||||
propertySetter.setFloat(mRecentsView.getSplitPlaceholder(), ALPHA_FLOAT,
|
||||
splitPlaceholderAlpha, LINEAR);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,52 +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.launcher3.uioverrides.plugins;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.systemui.shared.plugins.PluginInitializer;
|
||||
|
||||
public class PluginInitializerImpl implements PluginInitializer {
|
||||
@Override
|
||||
public Looper getBgLooper() {
|
||||
return MODEL_EXECUTOR.getLooper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPluginManagerInit() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getWhitelistedPlugins(Context context) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginEnablerImpl getPluginEnabler(Context context) {
|
||||
return new PluginEnablerImpl(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleWtfs() {
|
||||
}
|
||||
|
||||
public boolean isDebuggable() {
|
||||
return Utilities.IS_DEBUG_DEVICE;
|
||||
}
|
||||
}
|
||||
@@ -16,21 +16,29 @@ package com.android.launcher3.uioverrides.plugins;
|
||||
|
||||
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ResolveInfo;
|
||||
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.systemui.plugins.Plugin;
|
||||
import com.android.systemui.plugins.PluginListener;
|
||||
import com.android.systemui.shared.plugins.PluginActionManager;
|
||||
import com.android.systemui.shared.plugins.PluginInstance;
|
||||
import com.android.systemui.shared.plugins.PluginManager;
|
||||
import com.android.systemui.shared.plugins.PluginManagerImpl;
|
||||
import com.android.systemui.shared.plugins.PluginPrefs;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public class PluginManagerWrapper {
|
||||
@@ -46,21 +54,36 @@ public class PluginManagerWrapper {
|
||||
|
||||
private PluginManagerWrapper(Context c) {
|
||||
mContext = c;
|
||||
PluginInitializerImpl pluginInitializer = new PluginInitializerImpl();
|
||||
mPluginManager = new PluginManagerImpl(c, pluginInitializer);
|
||||
mPluginEnabler = pluginInitializer.getPluginEnabler(c);
|
||||
mPluginEnabler = new PluginEnablerImpl(c);
|
||||
List<String> privilegedPlugins = Collections.emptyList();
|
||||
PluginInstance.Factory instanceFactory = new PluginInstance.Factory(
|
||||
getClass().getClassLoader(), new PluginInstance.InstanceFactory<>(),
|
||||
new PluginInstance.VersionChecker(), privilegedPlugins,
|
||||
Utilities.IS_DEBUG_DEVICE);
|
||||
PluginActionManager.Factory instanceManagerFactory = new PluginActionManager.Factory(
|
||||
c, c.getPackageManager(), c.getMainExecutor(), MODEL_EXECUTOR,
|
||||
c.getSystemService(NotificationManager.class), mPluginEnabler,
|
||||
privilegedPlugins, instanceFactory);
|
||||
|
||||
mPluginManager = new PluginManagerImpl(c, instanceManagerFactory,
|
||||
Utilities.IS_DEBUG_DEVICE,
|
||||
Optional.ofNullable(Thread.getDefaultUncaughtExceptionHandler()), mPluginEnabler,
|
||||
new PluginPrefs(c), privilegedPlugins);
|
||||
}
|
||||
|
||||
public PluginEnablerImpl getPluginEnabler() {
|
||||
return mPluginEnabler;
|
||||
}
|
||||
|
||||
public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass) {
|
||||
/** */
|
||||
public <T extends Plugin> void addPluginListener(
|
||||
PluginListener<T> listener, Class<T> pluginClass) {
|
||||
addPluginListener(listener, pluginClass, false);
|
||||
}
|
||||
|
||||
public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass,
|
||||
boolean allowMultiple) {
|
||||
/** */
|
||||
public <T extends Plugin> void addPluginListener(
|
||||
PluginListener<T> listener, Class<T> pluginClass, boolean allowMultiple) {
|
||||
mPluginManager.addPluginListener(listener, pluginClass, allowMultiple);
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,11 @@ public class AllAppsState extends LauncherState {
|
||||
return scaleAndTranslation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTaskbarStashed(Launcher launcher) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getDepthUnchecked(Context context) {
|
||||
// The scrim fades in at approximately 50% of the swipe gesture.
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.graphics.Color;
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.allapps.AllAppsTransitionController;
|
||||
import com.android.quickstep.util.LayoutUtils;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
@@ -73,8 +74,7 @@ public class BackgroundAppState extends OverviewState {
|
||||
return super.getVisibleElements(launcher)
|
||||
& ~OVERVIEW_ACTIONS
|
||||
& ~CLEAR_ALL_BUTTON
|
||||
& ~VERTICAL_SWIPE_INDICATOR
|
||||
| TASKBAR;
|
||||
& ~VERTICAL_SWIPE_INDICATOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,6 +89,10 @@ public class BackgroundAppState extends OverviewState {
|
||||
|
||||
@Override
|
||||
public int getWorkspaceScrimColor(Launcher launcher) {
|
||||
DeviceProfile dp = launcher.getDeviceProfile();
|
||||
if (dp.isTaskbarPresentInApps) {
|
||||
return launcher.getColor(R.color.taskbar_background);
|
||||
}
|
||||
return Color.TRANSPARENT;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,14 +21,11 @@ import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERV
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.os.SystemProperties;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.quickstep.SysUINavigationMode;
|
||||
import com.android.quickstep.util.LayoutUtils;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
@@ -69,10 +66,7 @@ public class OverviewState extends LauncherState {
|
||||
@Override
|
||||
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
|
||||
RecentsView recentsView = launcher.getOverviewPanel();
|
||||
Workspace workspace = launcher.getWorkspace();
|
||||
View workspacePage = workspace.getPageAt(workspace.getCurrentPage());
|
||||
float workspacePageWidth = workspacePage != null && workspacePage.getWidth() != 0
|
||||
? workspacePage.getWidth() : launcher.getDeviceProfile().availableWidthPx;
|
||||
float workspacePageWidth = launcher.getDeviceProfile().getWorkspaceWidth();
|
||||
recentsView.getTaskSize(sTempRect);
|
||||
float scale = (float) sTempRect.width() / workspacePageWidth;
|
||||
float parallaxFactor = 0.5f;
|
||||
@@ -84,16 +78,6 @@ public class OverviewState extends LauncherState {
|
||||
return new float[] {NO_SCALE, NO_OFFSET};
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTaskbarScale(Launcher launcher) {
|
||||
return 1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTaskbarTranslationY(Launcher launcher) {
|
||||
return 0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
|
||||
return new PageAlphaProvider(DEACCEL_2) {
|
||||
@@ -112,6 +96,11 @@ public class OverviewState extends LauncherState {
|
||||
return CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTaskbarStashed(Launcher launcher) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWorkspaceScrimColor(Launcher launcher) {
|
||||
return ColorTokens.OverviewScrim.resolveColor(launcher);
|
||||
@@ -119,7 +108,7 @@ public class OverviewState extends LauncherState {
|
||||
|
||||
@Override
|
||||
public boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
|
||||
return deviceProfile.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
|
||||
return deviceProfile.overviewShowAsGrid;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,7 +17,9 @@ package com.android.launcher3.uioverrides.states;
|
||||
|
||||
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.R;
|
||||
|
||||
import app.lawnchair.theme.color.ColorTokens;
|
||||
|
||||
@@ -42,6 +44,10 @@ public class QuickSwitchState extends BackgroundAppState {
|
||||
|
||||
@Override
|
||||
public int getWorkspaceScrimColor(Launcher launcher) {
|
||||
DeviceProfile dp = launcher.getDeviceProfile();
|
||||
if (dp.isTaskbarPresentInApps) {
|
||||
return launcher.getColor(R.color.taskbar_background);
|
||||
}
|
||||
return ColorTokens.OverviewScrim.resolveColor(launcher);
|
||||
}
|
||||
|
||||
@@ -53,6 +59,16 @@ public class QuickSwitchState extends BackgroundAppState {
|
||||
|
||||
@Override
|
||||
public int getVisibleElements(Launcher launcher) {
|
||||
return TASKBAR;
|
||||
return NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTaskbarStashed(Launcher launcher) {
|
||||
return !launcher.getDeviceProfile().isTaskbarPresentInApps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTaskbarAlignedWithHotseat(Launcher launcher) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -17,6 +17,7 @@ package com.android.launcher3.uioverrides.states;
|
||||
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import static com.android.launcher3.LauncherState.ALL_APPS;
|
||||
import static com.android.launcher3.LauncherState.HINT_STATE;
|
||||
import static com.android.launcher3.LauncherState.HINT_STATE_TWO_BUTTON;
|
||||
import static com.android.launcher3.LauncherState.NORMAL;
|
||||
@@ -44,6 +45,10 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
|
||||
import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD;
|
||||
import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD;
|
||||
import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_SCRIM_OPAQUE_THRESHOLD;
|
||||
import static com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController.ALL_APPS_SCRIM_VISIBLE_THRESHOLD;
|
||||
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
@@ -52,6 +57,7 @@ import com.android.launcher3.CellLayout;
|
||||
import com.android.launcher3.Hotseat;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.quickstep.SysUINavigationMode;
|
||||
@@ -171,6 +177,13 @@ public class QuickstepAtomicAnimationFactory extends
|
||||
mHintToNormalDuration = (int) va.getDuration();
|
||||
}
|
||||
config.duration = Math.max(config.duration, mHintToNormalDuration);
|
||||
} else if (fromState == ALL_APPS && toState == NORMAL) {
|
||||
config.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(DEACCEL,
|
||||
1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD,
|
||||
1 - ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD));
|
||||
config.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(DEACCEL,
|
||||
1 - ALL_APPS_SCRIM_OPAQUE_THRESHOLD,
|
||||
1 - ALL_APPS_SCRIM_VISIBLE_THRESHOLD));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
package com.android.launcher3.uioverrides.states;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.touch.PagedOrientationHandler;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
|
||||
/**
|
||||
@@ -30,11 +28,6 @@ public class SplitScreenSelectState extends OverviewState {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed(Launcher launcher) {
|
||||
launcher.getStateManager().goToState(OVERVIEW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVisibleElements(Launcher launcher) {
|
||||
return SPLIT_PLACHOLDER_VIEW;
|
||||
@@ -43,13 +36,6 @@ public class SplitScreenSelectState extends OverviewState {
|
||||
@Override
|
||||
public float getSplitSelectTranslation(Launcher launcher) {
|
||||
RecentsView recentsView = launcher.getOverviewPanel();
|
||||
int splitPosition = recentsView.getSplitPlaceholder().getSplitController()
|
||||
.getActiveSplitPositionOption().mStagePosition;
|
||||
if (!recentsView.shouldShiftThumbnailsForSplitSelect(splitPosition)) {
|
||||
return 0f;
|
||||
}
|
||||
PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
|
||||
int direction = orientationHandler.getSplitTranslationDirectionFactor(splitPosition);
|
||||
return launcher.getResources().getDimension(R.dimen.split_placeholder_size) * direction;
|
||||
return recentsView.getSplitSelectTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
|
||||
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
|
||||
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
|
||||
import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
@@ -38,11 +38,11 @@ import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.launcher3.util.VibratorWrapper;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.util.AnimatorControllerWithResistance;
|
||||
import com.android.quickstep.util.MotionPauseDetector;
|
||||
import com.android.quickstep.util.OverviewToHomeAnim;
|
||||
import com.android.quickstep.util.VibratorWrapper;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
|
||||
/**
|
||||
|
||||
+20
-3
@@ -41,7 +41,7 @@ import static com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM;
|
||||
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
|
||||
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
|
||||
import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
|
||||
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
|
||||
import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
|
||||
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
|
||||
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
|
||||
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
|
||||
@@ -67,14 +67,15 @@ import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.launcher3.touch.BaseSwipeDetector;
|
||||
import com.android.launcher3.touch.BothAxesSwipeDetector;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.launcher3.util.VibratorWrapper;
|
||||
import com.android.quickstep.AnimatedFloat;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.util.AnimatorControllerWithResistance;
|
||||
import com.android.quickstep.util.LayoutUtils;
|
||||
import com.android.quickstep.util.MotionPauseDetector;
|
||||
import com.android.quickstep.util.VibratorWrapper;
|
||||
import com.android.quickstep.util.WorkspaceRevealAnim;
|
||||
import com.android.quickstep.views.LauncherRecentsView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
|
||||
/**
|
||||
* Handles quick switching to a recent task from the home screen. To give as much flexibility to
|
||||
@@ -235,8 +236,10 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
// - RecentsView fade (if it's empty)
|
||||
PendingAnimation xAnim = new PendingAnimation((long) (mXRange * 2));
|
||||
xAnim.setFloat(mRecentsView, ADJACENT_PAGE_HORIZONTAL_OFFSET, scaleAndOffset[1], LINEAR);
|
||||
// Use QuickSwitchState instead of OverviewState to determine scrim color,
|
||||
// since we need to take potential taskbar into account.
|
||||
xAnim.setViewBackgroundColor(mLauncher.getScrimView(),
|
||||
toState.getWorkspaceScrimColor(mLauncher), LINEAR);
|
||||
QUICK_SWITCH.getWorkspaceScrimColor(mLauncher), LINEAR);
|
||||
if (mRecentsView.getTaskViewCount() == 0) {
|
||||
xAnim.addFloat(mRecentsView, CONTENT_ALPHA, 0f, 1f, LINEAR);
|
||||
}
|
||||
@@ -309,6 +312,11 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
}
|
||||
});
|
||||
overviewAnim.start();
|
||||
|
||||
// Create an empty state transition so StateListeners get onStateTransitionStart().
|
||||
mLauncher.getStateManager().createAnimationToNewWorkspace(
|
||||
OVERVIEW, config.duration, StateAnimationConfig.SKIP_ALL_ANIMATIONS)
|
||||
.dispatchOnStart();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -383,6 +391,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
config.animFlags = SKIP_ALL_ANIMATIONS;
|
||||
updateNonOverviewAnim(targetState, config);
|
||||
nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
|
||||
mNonOverviewAnim.dispatchOnStart();
|
||||
|
||||
new WorkspaceRevealAnim(mLauncher, false /* animateOverviewScrim */).start();
|
||||
} else {
|
||||
@@ -398,6 +407,14 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
nonOverviewAnim.setFloatValues(startProgress, endProgress);
|
||||
mNonOverviewAnim.dispatchOnStart();
|
||||
}
|
||||
if (targetState == QUICK_SWITCH) {
|
||||
// Navigating to quick switch, add scroll feedback since the first time is not
|
||||
// considered a scroll by the RecentsView.
|
||||
VibratorWrapper.INSTANCE.get(mLauncher).vibrate(
|
||||
RecentsView.SCROLL_VIBRATION_PRIMITIVE,
|
||||
RecentsView.SCROLL_VIBRATION_PRIMITIVE_SCALE,
|
||||
RecentsView.SCROLL_VIBRATION_FALLBACK);
|
||||
}
|
||||
|
||||
nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
|
||||
mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState));
|
||||
|
||||
+4
-4
@@ -52,22 +52,22 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
|
||||
/**
|
||||
* The progress at which all apps content will be fully visible.
|
||||
*/
|
||||
protected static final float ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD = 0.8f;
|
||||
public static final float ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD = 0.8f;
|
||||
|
||||
/**
|
||||
* Minimum clamping progress for fading in all apps content
|
||||
*/
|
||||
protected static final float ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD = 0.5f;
|
||||
public static final float ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD = 0.5f;
|
||||
|
||||
/**
|
||||
* Minimum clamping progress for fading in all apps scrim
|
||||
*/
|
||||
protected static final float ALL_APPS_SCRIM_VISIBLE_THRESHOLD = .1f;
|
||||
public static final float ALL_APPS_SCRIM_VISIBLE_THRESHOLD = .1f;
|
||||
|
||||
/**
|
||||
* Maximum clamping progress for opaque all apps scrim
|
||||
*/
|
||||
protected static final float ALL_APPS_SCRIM_OPAQUE_THRESHOLD = .5f;
|
||||
public static final float ALL_APPS_SCRIM_OPAQUE_THRESHOLD = .5f;
|
||||
|
||||
private final PortraitOverviewStateTouchHelper mOverviewPortraitStateTouchHelper;
|
||||
|
||||
|
||||
+10
-11
@@ -21,6 +21,7 @@ import static android.view.MotionEvent.ACTION_CANCEL;
|
||||
import static android.view.MotionEvent.ACTION_DOWN;
|
||||
import static android.view.MotionEvent.ACTION_MOVE;
|
||||
import static android.view.MotionEvent.ACTION_UP;
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
|
||||
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPE_DOWN_WORKSPACE_NOTISHADE_OPEN;
|
||||
|
||||
@@ -53,17 +54,6 @@ public class StatusBarTouchController implements TouchController {
|
||||
|
||||
private static final String TAG = "StatusBarController";
|
||||
|
||||
/**
|
||||
* Window flag: Enable touches to slide out of a window into neighboring
|
||||
* windows in mid-gesture instead of being captured for the duration of
|
||||
* the gesture.
|
||||
*
|
||||
* This flag changes the behavior of touch focus for this window only.
|
||||
* Touches can slide out of the window but they cannot necessarily slide
|
||||
* back in (unless the other window with touch focus permits it).
|
||||
*/
|
||||
private static final int FLAG_SLIPPERY = 0x20000000;
|
||||
|
||||
private final Launcher mLauncher;
|
||||
private final SystemUiProxy mSystemUiProxy;
|
||||
private final float mTouchSlop;
|
||||
@@ -165,6 +155,15 @@ public class StatusBarTouchController implements TouchController {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* FLAG_SLIPPERY enables touches to slide out of a window into neighboring
|
||||
* windows in mid-gesture instead of being captured for the duration of
|
||||
* the gesture.
|
||||
*
|
||||
* This flag changes the behavior of touch focus for this window only.
|
||||
* Touches can slide out of the window but they cannot necessarily slide
|
||||
* back in (unless the other window with touch focus permits it).
|
||||
*/
|
||||
private void setWindowSlippery(boolean enable) {
|
||||
Window w = mLauncher.getWindow();
|
||||
WindowManager.LayoutParams wlp = w.getAttributes();
|
||||
|
||||
+34
-23
@@ -22,6 +22,7 @@ import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.os.SystemClock;
|
||||
import android.os.VibrationEffect;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.animation.Interpolator;
|
||||
@@ -34,7 +35,6 @@ import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.touch.BaseSwipeDetector;
|
||||
import com.android.launcher3.touch.PagedOrientationHandler;
|
||||
import com.android.launcher3.touch.SingleAxisSwipeDetector;
|
||||
@@ -42,6 +42,7 @@ import com.android.launcher3.util.FlingBlockCheck;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
import com.android.quickstep.SysUINavigationMode;
|
||||
import com.android.quickstep.util.VibratorWrapper;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
|
||||
@@ -56,6 +57,12 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
|
||||
private static final long MIN_TASK_DISMISS_ANIMATION_DURATION = 300;
|
||||
private static final long MAX_TASK_DISMISS_ANIMATION_DURATION = 600;
|
||||
|
||||
public static final int TASK_DISMISS_VIBRATION_PRIMITIVE =
|
||||
Utilities.ATLEAST_R ? VibrationEffect.Composition.PRIMITIVE_TICK : -1;
|
||||
public static final float TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE = 1f;
|
||||
public static final VibrationEffect TASK_DISMISS_VIBRATION_FALLBACK =
|
||||
VibratorWrapper.EFFECT_TEXTURE_TICK;
|
||||
|
||||
protected final T mActivity;
|
||||
private final SingleAxisSwipeDetector mDetector;
|
||||
private final RecentsView mRecentsView;
|
||||
@@ -77,6 +84,8 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
|
||||
|
||||
private TaskView mTaskBeingDragged;
|
||||
|
||||
private boolean mIsDismissHapticRunning = false;
|
||||
|
||||
public TaskViewTouchController(T activity) {
|
||||
mActivity = activity;
|
||||
mRecentsView = activity.getOverviewPanel();
|
||||
@@ -158,26 +167,21 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
|
||||
mTaskBeingDragged = view;
|
||||
int upDirection = mRecentsView.getPagedOrientationHandler()
|
||||
.getUpDirection(mIsRtl);
|
||||
if (!SysUINavigationMode.getMode(mActivity).hasGestures || (
|
||||
mActivity.getDeviceProfile().isTablet
|
||||
&& FeatureFlags.ENABLE_OVERVIEW_GRID.get())) {
|
||||
// Don't allow swipe down to open if we don't support swipe up
|
||||
// to enter overview, or when grid layout is enabled.
|
||||
directionsToDetectScroll = upDirection;
|
||||
mAllowGoingUp = true;
|
||||
mAllowGoingDown = false;
|
||||
} else {
|
||||
// The task can be dragged up to dismiss it,
|
||||
// and down to open if it's the current page.
|
||||
mAllowGoingUp = true;
|
||||
if (i == mRecentsView.getCurrentPage()) {
|
||||
mAllowGoingDown = true;
|
||||
directionsToDetectScroll = DIRECTION_BOTH;
|
||||
} else {
|
||||
mAllowGoingDown = false;
|
||||
directionsToDetectScroll = upDirection;
|
||||
}
|
||||
}
|
||||
|
||||
// The task can be dragged up to dismiss it
|
||||
mAllowGoingUp = true;
|
||||
|
||||
// The task can be dragged down to open it if:
|
||||
// - It's the current page
|
||||
// - We support gestures to enter overview
|
||||
// - It's the focused task if in grid view
|
||||
// - The task is snapped
|
||||
mAllowGoingDown = i == mRecentsView.getCurrentPage()
|
||||
&& SysUINavigationMode.getMode(mActivity).hasGestures
|
||||
&& (!mRecentsView.showAsGrid() || mTaskBeingDragged.isFocusedTask())
|
||||
&& mRecentsView.isTaskInExpectedScrollPosition(i);
|
||||
|
||||
directionsToDetectScroll = mAllowGoingDown ? DIRECTION_BOTH : upDirection;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -233,7 +237,8 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
|
||||
if (goingUp) {
|
||||
currentInterpolator = Interpolators.LINEAR;
|
||||
pa = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
|
||||
true /* animateTaskView */, true /* removeTask */, maxDuration);
|
||||
true /* animateTaskView */, true /* removeTask */, maxDuration,
|
||||
false /* dismissingForSplitSelection*/);
|
||||
|
||||
mEndDisplacement = -secondaryTaskDimension;
|
||||
} else {
|
||||
@@ -339,10 +344,10 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
|
||||
fling = false;
|
||||
}
|
||||
PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
|
||||
boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
|
||||
float progress = mCurrentAnimation.getProgressFraction();
|
||||
float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
|
||||
if (fling) {
|
||||
boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
|
||||
goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
|
||||
} else {
|
||||
goingToEnd = interpolatedProgress > SUCCESS_TRANSITION_PROGRESS;
|
||||
@@ -362,6 +367,11 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
|
||||
mCurrentAnimation.startWithVelocity(mActivity, goingToEnd,
|
||||
velocity * orientationHandler.getSecondaryTranslationDirectionFactor(),
|
||||
mEndDisplacement, animationDuration);
|
||||
if (goingUp && goingToEnd && !mIsDismissHapticRunning) {
|
||||
VibratorWrapper.INSTANCE.get(mActivity).vibrate(TASK_DISMISS_VIBRATION_PRIMITIVE,
|
||||
TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE, TASK_DISMISS_VIBRATION_FALLBACK);
|
||||
mIsDismissHapticRunning = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void clearState() {
|
||||
@@ -369,5 +379,6 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
|
||||
mDetector.setDetectableScrollConditions(0, false);
|
||||
mTaskBeingDragged = null;
|
||||
mCurrentAnimation = null;
|
||||
mIsDismissHapticRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
|
||||
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.SystemUiController.UI_STATE_FULLSCREEN_TASK;
|
||||
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
|
||||
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;
|
||||
@@ -46,6 +45,7 @@ import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
|
||||
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_CANCELED;
|
||||
import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
|
||||
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
|
||||
import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
|
||||
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.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
|
||||
@@ -55,6 +55,7 @@ import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -73,6 +74,7 @@ import android.view.ViewTreeObserver.OnScrollChangedListener;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.Toast;
|
||||
import android.window.PictureInPictureSurfaceTransaction;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
@@ -90,16 +92,18 @@ import com.android.launcher3.statemanager.BaseState;
|
||||
import com.android.launcher3.statemanager.StatefulActivity;
|
||||
import com.android.launcher3.tracing.InputConsumerProto;
|
||||
import com.android.launcher3.tracing.SwipeHandlerProto;
|
||||
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
|
||||
import com.android.launcher3.util.TraceHelper;
|
||||
import com.android.launcher3.util.VibratorWrapper;
|
||||
import com.android.launcher3.util.WindowBounds;
|
||||
import com.android.quickstep.BaseActivityInterface.AnimationFactory;
|
||||
import com.android.quickstep.GestureState.GestureEndTarget;
|
||||
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
|
||||
import com.android.quickstep.util.ActiveGestureLog;
|
||||
import com.android.quickstep.util.ActivityInitListener;
|
||||
import com.android.quickstep.util.AnimatorControllerWithResistance;
|
||||
import com.android.quickstep.util.InputConsumerProxy;
|
||||
import com.android.quickstep.util.InputProxyHandlerFactory;
|
||||
import com.android.quickstep.util.LauncherSplitScreenListener;
|
||||
import com.android.quickstep.util.MotionPauseDetector;
|
||||
import com.android.quickstep.util.ProtoTracer;
|
||||
import com.android.quickstep.util.RecentsOrientedState;
|
||||
@@ -107,7 +111,8 @@ import com.android.quickstep.util.RectFSpringAnim;
|
||||
import com.android.quickstep.util.StaggeredWorkspaceAnim;
|
||||
import com.android.quickstep.util.SurfaceTransactionApplier;
|
||||
import com.android.quickstep.util.SwipePipToHomeAnimator;
|
||||
import com.android.quickstep.util.TransformParams;
|
||||
import com.android.quickstep.util.TaskViewSimulator;
|
||||
import com.android.quickstep.util.VibratorWrapper;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
@@ -120,6 +125,8 @@ import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListeners;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import app.lawnchair.util.CompatibilityKt;
|
||||
@@ -152,6 +159,17 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
protected MultiStateCallback mStateCallback;
|
||||
protected boolean mCanceled;
|
||||
private boolean mRecentsViewScrollLinked = false;
|
||||
private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks =
|
||||
new ActivityLifecycleCallbacksAdapter() {
|
||||
@Override
|
||||
public void onActivityDestroyed(Activity activity) {
|
||||
if (mActivity != activity) {
|
||||
return;
|
||||
}
|
||||
mRecentsView = null;
|
||||
mActivity = null;
|
||||
}
|
||||
};
|
||||
|
||||
private static int getFlagForIndex(int index, String name) {
|
||||
if (DEBUG_STATES) {
|
||||
@@ -165,45 +183,51 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
|
||||
protected static final int STATE_LAUNCHER_STARTED =
|
||||
getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
|
||||
protected static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
|
||||
protected static final int STATE_LAUNCHER_DRAWN =
|
||||
getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
|
||||
// Called when the Launcher has connected to the touch interaction service (and the taskbar
|
||||
// ui controller is initialized)
|
||||
protected static final int STATE_LAUNCHER_BIND_TO_SERVICE =
|
||||
getFlagForIndex(3, "STATE_LAUNCHER_BIND_TO_SERVICE");
|
||||
|
||||
// Internal initialization states
|
||||
private static final int STATE_APP_CONTROLLER_RECEIVED =
|
||||
getFlagForIndex(3, "STATE_APP_CONTROLLER_RECEIVED");
|
||||
getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
|
||||
|
||||
// Interaction finish states
|
||||
private static final int STATE_SCALED_CONTROLLER_HOME =
|
||||
getFlagForIndex(4, "STATE_SCALED_CONTROLLER_HOME");
|
||||
getFlagForIndex(5, "STATE_SCALED_CONTROLLER_HOME");
|
||||
private static final int STATE_SCALED_CONTROLLER_RECENTS =
|
||||
getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS");
|
||||
getFlagForIndex(6, "STATE_SCALED_CONTROLLER_RECENTS");
|
||||
|
||||
protected static final int STATE_HANDLER_INVALIDATED =
|
||||
getFlagForIndex(6, "STATE_HANDLER_INVALIDATED");
|
||||
getFlagForIndex(7, "STATE_HANDLER_INVALIDATED");
|
||||
private static final int STATE_GESTURE_STARTED =
|
||||
getFlagForIndex(7, "STATE_GESTURE_STARTED");
|
||||
getFlagForIndex(8, "STATE_GESTURE_STARTED");
|
||||
private static final int STATE_GESTURE_CANCELLED =
|
||||
getFlagForIndex(8, "STATE_GESTURE_CANCELLED");
|
||||
getFlagForIndex(9, "STATE_GESTURE_CANCELLED");
|
||||
private static final int STATE_GESTURE_COMPLETED =
|
||||
getFlagForIndex(9, "STATE_GESTURE_COMPLETED");
|
||||
getFlagForIndex(10, "STATE_GESTURE_COMPLETED");
|
||||
|
||||
private static final int STATE_CAPTURE_SCREENSHOT =
|
||||
getFlagForIndex(10, "STATE_CAPTURE_SCREENSHOT");
|
||||
getFlagForIndex(11, "STATE_CAPTURE_SCREENSHOT");
|
||||
protected static final int STATE_SCREENSHOT_CAPTURED =
|
||||
getFlagForIndex(11, "STATE_SCREENSHOT_CAPTURED");
|
||||
getFlagForIndex(12, "STATE_SCREENSHOT_CAPTURED");
|
||||
private static final int STATE_SCREENSHOT_VIEW_SHOWN =
|
||||
getFlagForIndex(12, "STATE_SCREENSHOT_VIEW_SHOWN");
|
||||
getFlagForIndex(13, "STATE_SCREENSHOT_VIEW_SHOWN");
|
||||
|
||||
private static final int STATE_RESUME_LAST_TASK =
|
||||
getFlagForIndex(13, "STATE_RESUME_LAST_TASK");
|
||||
getFlagForIndex(14, "STATE_RESUME_LAST_TASK");
|
||||
private static final int STATE_START_NEW_TASK =
|
||||
getFlagForIndex(14, "STATE_START_NEW_TASK");
|
||||
getFlagForIndex(15, "STATE_START_NEW_TASK");
|
||||
private static final int STATE_CURRENT_TASK_FINISHED =
|
||||
getFlagForIndex(15, "STATE_CURRENT_TASK_FINISHED");
|
||||
getFlagForIndex(16, "STATE_CURRENT_TASK_FINISHED");
|
||||
private static final int STATE_FINISH_WITH_NO_END =
|
||||
getFlagForIndex(16, "STATE_FINISH_WITH_NO_END");
|
||||
getFlagForIndex(17, "STATE_FINISH_WITH_NO_END");
|
||||
|
||||
private static final int LAUNCHER_UI_STATES =
|
||||
STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
|
||||
STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED |
|
||||
STATE_LAUNCHER_BIND_TO_SERVICE;
|
||||
|
||||
public static final long MAX_SWIPE_DURATION = 350;
|
||||
public static final long HOME_DURATION = StaggeredWorkspaceAnim.DURATION_MS;
|
||||
@@ -215,6 +239,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
|
||||
public static final long RECENTS_ATTACH_DURATION = 300;
|
||||
|
||||
private static final float MAX_QUICK_SWITCH_RECENTS_SCALE_PROGRESS = 0.07f;
|
||||
|
||||
/**
|
||||
* Used as the page index for logging when we return to the last task at the end of the gesture.
|
||||
*/
|
||||
@@ -223,7 +249,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
protected final TaskAnimationManager mTaskAnimationManager;
|
||||
|
||||
// Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
|
||||
private RunningWindowAnim mRunningWindowAnim;
|
||||
private RunningWindowAnim[] mRunningWindowAnim;
|
||||
// Possible second animation running at the same time as mRunningWindowAnim
|
||||
private Animator mParallelRunningAnim;
|
||||
private boolean mIsMotionPaused;
|
||||
@@ -254,22 +280,33 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
|
||||
private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
|
||||
protected boolean mIsSwipingPipToHome;
|
||||
// TODO(b/195473090) no split PIP for now, remove once we have more clarity
|
||||
// can try to have RectFSpringAnim evaluate multiple rects at once
|
||||
private final SwipePipToHomeAnimator[] mSwipePipToHomeAnimators =
|
||||
new SwipePipToHomeAnimator[2];
|
||||
|
||||
// Interpolate RecentsView scale from start of quick switch scroll until this scroll threshold
|
||||
private final float mQuickSwitchScaleScrollThreshold;
|
||||
|
||||
public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
|
||||
TaskAnimationManager taskAnimationManager, GestureState gestureState,
|
||||
long touchTimeMs, boolean continuingLastGesture,
|
||||
InputConsumerController inputConsumer) {
|
||||
super(context, deviceState, gestureState, new TransformParams());
|
||||
super(context, deviceState, gestureState);
|
||||
mActivityInterface = gestureState.getActivityInterface();
|
||||
mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
|
||||
mInputConsumerProxy =
|
||||
new InputConsumerProxy(inputConsumer, () -> {
|
||||
new InputConsumerProxy(context,
|
||||
() -> mRecentsView.getPagedViewOrientedState().getRecentsActivityRotation(),
|
||||
inputConsumer, () -> {
|
||||
endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
|
||||
endLauncherTransitionController();
|
||||
}, new InputProxyHandlerFactory(mActivityInterface, mGestureState));
|
||||
mTaskAnimationManager = taskAnimationManager;
|
||||
mTouchTimeMs = touchTimeMs;
|
||||
mContinuingLastGesture = continuingLastGesture;
|
||||
mQuickSwitchScaleScrollThreshold = context.getResources().getDimension(
|
||||
R.dimen.quick_switch_scaling_scroll_threshold);
|
||||
|
||||
initAfterSubclassConstructor();
|
||||
initStateCallbacks();
|
||||
@@ -387,9 +424,10 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
// Set up a entire animation lifecycle callback to notify the current recents view when
|
||||
// the animation is canceled
|
||||
mGestureState.runOnceAtState(STATE_RECENTS_ANIMATION_CANCELED, () -> {
|
||||
ThumbnailData snapshot = mGestureState.consumeRecentsAnimationCanceledSnapshot();
|
||||
if (snapshot != null) {
|
||||
mRecentsView.switchToScreenshot(snapshot, () -> {
|
||||
HashMap<Integer, ThumbnailData> snapshots =
|
||||
mGestureState.consumeRecentsAnimationCanceledSnapshot();
|
||||
if (snapshots != null) {
|
||||
mRecentsView.switchToScreenshot(snapshots, () -> {
|
||||
if (mRecentsAnimationController != null) {
|
||||
mRecentsAnimationController.cleanupScreenshot();
|
||||
}
|
||||
@@ -400,7 +438,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
|
||||
setupRecentsViewUi();
|
||||
linkRecentsViewScroll();
|
||||
activity.runOnBindToTouchInteractionService(this::onLauncherBindToService);
|
||||
|
||||
mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -422,7 +462,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
// RecentsView never updates the display rotation until swipe-up, force update
|
||||
// RecentsOrientedState before passing to TaskViewSimulator.
|
||||
mRecentsView.updateRecentsRotation();
|
||||
mTaskViewSimulator.setOrientationState(mRecentsView.getPagedViewOrientedState());
|
||||
runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
|
||||
.setOrientationState(mRecentsView.getPagedViewOrientedState()));
|
||||
|
||||
// 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.
|
||||
@@ -431,6 +472,10 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
mAnimationFactory = mActivityInterface.prepareRecentsUI(mDeviceState,
|
||||
mWasLauncherAlreadyVisible, this::onAnimatorPlaybackControllerCreated);
|
||||
maybeUpdateRecentsAttachedState(false /* animate */);
|
||||
if (mGestureState.getEndTarget() != null) {
|
||||
// Update the end target in case the gesture ended before we init.
|
||||
mAnimationFactory.setEndTarget(mGestureState.getEndTarget());
|
||||
}
|
||||
};
|
||||
if (mWasLauncherAlreadyVisible) {
|
||||
// Launcher is visible, but might be about to stop. Thus, if we prepare recents
|
||||
@@ -475,6 +520,11 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
mStateCallback.setState(STATE_LAUNCHER_STARTED);
|
||||
}
|
||||
|
||||
private void onLauncherBindToService() {
|
||||
mStateCallback.setState(STATE_LAUNCHER_BIND_TO_SERVICE);
|
||||
flushOnRecentsAnimationAndLauncherBound();
|
||||
}
|
||||
|
||||
private void onLauncherPresentAndGestureStarted() {
|
||||
// Re-setup the recents UI when gesture starts, as the state could have been changed during
|
||||
// that time by a previous window transition.
|
||||
@@ -515,7 +565,26 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
}
|
||||
|
||||
protected void notifyGestureAnimationStartToRecents() {
|
||||
mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask());
|
||||
ActivityManager.RunningTaskInfo[] runningTasks;
|
||||
if (mIsSwipeForStagedSplit) {
|
||||
int[] splitTaskIds =
|
||||
LauncherSplitScreenListener.INSTANCE.getNoCreate().getRunningSplitTaskIds();
|
||||
runningTasks = new ActivityManager.RunningTaskInfo[splitTaskIds.length];
|
||||
for (int i = 0; i < splitTaskIds.length; i++) {
|
||||
int taskId = splitTaskIds[i];
|
||||
// Order matters here, we want first indexed RunningTaskInfo to be leftTop task
|
||||
for (ActivityManager.RunningTaskInfo rti : mGestureState.getRunningTasks()) {
|
||||
if (taskId == rti.taskId) {
|
||||
runningTasks[i] = rti;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
runningTasks = new ActivityManager.RunningTaskInfo[]{mGestureState.getRunningTask()};
|
||||
}
|
||||
mRecentsView.onGestureAnimationStart(runningTasks);
|
||||
}
|
||||
|
||||
private void launcherFrameDrawn() {
|
||||
@@ -547,7 +616,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
@Override
|
||||
public void onMotionPauseDetected() {
|
||||
mHasMotionEverBeenPaused = true;
|
||||
maybeUpdateRecentsAttachedState();
|
||||
maybeUpdateRecentsAttachedState(true/* animate */, true/* moveFocusedTask */);
|
||||
performHapticFeedback();
|
||||
}
|
||||
|
||||
@@ -558,18 +627,24 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
};
|
||||
}
|
||||
|
||||
public void maybeUpdateRecentsAttachedState() {
|
||||
private void maybeUpdateRecentsAttachedState() {
|
||||
maybeUpdateRecentsAttachedState(true /* animate */);
|
||||
}
|
||||
|
||||
private void maybeUpdateRecentsAttachedState(boolean animate) {
|
||||
maybeUpdateRecentsAttachedState(animate, false /* moveFocusedTask */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether to show or hide RecentsView. The window is always
|
||||
* synchronized with its corresponding TaskView in RecentsView, so if
|
||||
* RecentsView is shown, it will appear to be attached to the window.
|
||||
*
|
||||
* Note this method has no effect unless the navigation mode is NO_BUTTON.
|
||||
* @param animate whether to animate when attaching RecentsView
|
||||
* @param moveFocusedTask whether to move focused task to front when attaching
|
||||
*/
|
||||
private void maybeUpdateRecentsAttachedState(boolean animate) {
|
||||
private void maybeUpdateRecentsAttachedState(boolean animate, boolean moveFocusedTask) {
|
||||
if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) {
|
||||
return;
|
||||
}
|
||||
@@ -588,6 +663,12 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
} else {
|
||||
recentsAttachedToAppWindow = mHasMotionEverBeenPaused || mIsLikelyToStartNewTask;
|
||||
}
|
||||
if (moveFocusedTask && !mAnimationFactory.hasRecentsEverAttachedToAppWindow()
|
||||
&& recentsAttachedToAppWindow) {
|
||||
// Only move focused task if RecentsView has never been attached before, to avoid
|
||||
// TaskView jumping to new position as we move the tasks.
|
||||
mRecentsView.moveFocusedTaskToFront();
|
||||
}
|
||||
mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
|
||||
|
||||
// Reapply window transform throughout the attach animation, as the animation affects how
|
||||
@@ -595,15 +676,15 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
if (animate) {
|
||||
ValueAnimator reapplyWindowTransformAnim = ValueAnimator.ofFloat(0, 1);
|
||||
reapplyWindowTransformAnim.addUpdateListener(anim -> {
|
||||
if (mRunningWindowAnim == null) {
|
||||
applyWindowTransform();
|
||||
if (mRunningWindowAnim == null || mRunningWindowAnim.length == 0) {
|
||||
applyScrollAndTransform();
|
||||
}
|
||||
});
|
||||
reapplyWindowTransformAnim.setDuration(RECENTS_ATTACH_DURATION).start();
|
||||
mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED,
|
||||
reapplyWindowTransformAnim::cancel);
|
||||
} else {
|
||||
applyWindowTransform();
|
||||
applyScrollAndTransform();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,13 +720,24 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
|
||||
WindowInsets result = view.onApplyWindowInsets(windowInsets);
|
||||
buildAnimationController();
|
||||
// Reapply the current shift to ensure it takes new insets into account, e.g. when long
|
||||
// pressing to stash taskbar without moving the finger.
|
||||
updateFinalShift();
|
||||
return result;
|
||||
}
|
||||
|
||||
private void onAnimatorPlaybackControllerCreated(AnimatorControllerWithResistance anim) {
|
||||
boolean isFirstCreation = mLauncherTransitionController == null;
|
||||
mLauncherTransitionController = anim;
|
||||
mLauncherTransitionController.getNormalController().dispatchOnStart();
|
||||
updateLauncherTransitionProgress();
|
||||
if (isFirstCreation) {
|
||||
mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, () -> {
|
||||
// Wait until the gesture is started (touch slop was passed) to start in sync with
|
||||
// mWindowTransitionController. This ensures we don't hide the taskbar background
|
||||
// when long pressing to stash it, for instance.
|
||||
mLauncherTransitionController.getNormalController().dispatchOnStart();
|
||||
updateLauncherTransitionProgress();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public Intent getLaunchIntent() {
|
||||
@@ -667,7 +759,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
}
|
||||
|
||||
updateSysUiFlags(mCurrentShift.value);
|
||||
applyWindowTransform();
|
||||
applyScrollAndTransform();
|
||||
|
||||
updateLauncherTransitionProgress();
|
||||
}
|
||||
@@ -677,7 +769,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
|| !canCreateNewOrUpdateExistingLauncherTransitionController()) {
|
||||
return;
|
||||
}
|
||||
mLauncherTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor);
|
||||
mLauncherTransitionController.setProgress(
|
||||
Math.max(mCurrentShift.value, getScaleProgressDueToScroll()), mDragLengthFactor);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -712,24 +805,25 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
@Override
|
||||
public void onRecentsAnimationStart(RecentsAnimationController controller,
|
||||
RecentsAnimationTargets targets) {
|
||||
super.onRecentsAnimationStart(controller, targets);
|
||||
ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
|
||||
mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(targets);
|
||||
mRecentsAnimationController = controller;
|
||||
mRecentsAnimationTargets = targets;
|
||||
mTransformParams.setTargetSet(mRecentsAnimationTargets);
|
||||
RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
|
||||
mGestureState.getRunningTaskId());
|
||||
|
||||
if (runningTaskTarget != null) {
|
||||
mTaskViewSimulator.setPreview(runningTaskTarget);
|
||||
}
|
||||
|
||||
// Only initialize the device profile, if it has not been initialized before, as in some
|
||||
// configurations targets.homeContentInsets may not be correct.
|
||||
if (mActivity == null) {
|
||||
DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
|
||||
if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
|
||||
RemoteAnimationTargetCompat primaryTaskTarget = targets.apps[0];
|
||||
// orientation state is independent of which remote target handle we use since both
|
||||
// should be pointing to the same one. Just choose index 0 for now since that works for
|
||||
// both split and non-split
|
||||
RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
|
||||
.getOrientationState();
|
||||
DeviceProfile dp = orientationState.getLauncherDeviceProfile();
|
||||
if (targets.minimizedHomeBounds != null && primaryTaskTarget != null) {
|
||||
Rect overviewStackBounds = mActivityInterface
|
||||
.getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
|
||||
.getOverviewWindowBounds(targets.minimizedHomeBounds, primaryTaskTarget);
|
||||
dp = dp.getMultiWindowProfile(mContext,
|
||||
new WindowBounds(overviewStackBounds, targets.homeContentInsets));
|
||||
} else {
|
||||
@@ -739,16 +833,14 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
dp.updateInsets(targets.homeContentInsets);
|
||||
dp.updateIsSeascape(mContext);
|
||||
initTransitionEndpoints(dp);
|
||||
mTaskViewSimulator.getOrientationState().setMultiWindowMode(dp.isMultiWindowMode);
|
||||
orientationState.setMultiWindowMode(dp.isMultiWindowMode);
|
||||
}
|
||||
|
||||
// Notify when the animation starts
|
||||
if (!mRecentsAnimationStartCallbacks.isEmpty()) {
|
||||
for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
|
||||
action.run();
|
||||
}
|
||||
mRecentsAnimationStartCallbacks.clear();
|
||||
}
|
||||
flushOnRecentsAnimationAndLauncherBound();
|
||||
|
||||
TaskViewUtils.setSplitAuxiliarySurfacesShown(mRecentsAnimationTargets.nonApps,
|
||||
false /*shown*/, true /*animate*/);
|
||||
|
||||
// Only add the callback to enable the input consumer after we actually have the controller
|
||||
mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
|
||||
@@ -759,11 +851,16 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
|
||||
public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
|
||||
ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
|
||||
mActivityInitListener.unregister();
|
||||
mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
|
||||
|
||||
if (mRecentsAnimationTargets != null) {
|
||||
TaskViewUtils.setSplitAuxiliarySurfacesShown(mRecentsAnimationTargets.nonApps,
|
||||
true /*shown*/, true /*animate*/);
|
||||
}
|
||||
|
||||
// Defer clearing the controller and the targets until after we've updated the state
|
||||
mRecentsAnimationController = null;
|
||||
mRecentsAnimationTargets = null;
|
||||
@@ -853,9 +950,17 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
private void endRunningWindowAnim(boolean cancel) {
|
||||
if (mRunningWindowAnim != null) {
|
||||
if (cancel) {
|
||||
mRunningWindowAnim.cancel();
|
||||
for (RunningWindowAnim r : mRunningWindowAnim) {
|
||||
if (r != null) {
|
||||
r.cancel();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mRunningWindowAnim.end();
|
||||
for (RunningWindowAnim r : mRunningWindowAnim) {
|
||||
if (r != null) {
|
||||
r.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mParallelRunningAnim != null) {
|
||||
@@ -869,6 +974,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
// Fast-finish the attaching animation if it's still running.
|
||||
maybeUpdateRecentsAttachedState(false);
|
||||
final GestureEndTarget endTarget = mGestureState.getEndTarget();
|
||||
// Wait until the given View (if supplied) draws before resuming the last task.
|
||||
View postResumeLastTask = mActivityInterface.onSettledOnEndTarget(endTarget);
|
||||
|
||||
if (endTarget != NEW_TASK) {
|
||||
InteractionJankMonitorWrapper.cancel(
|
||||
InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
|
||||
@@ -877,11 +985,13 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
InteractionJankMonitorWrapper.cancel(
|
||||
InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
|
||||
}
|
||||
|
||||
switch (endTarget) {
|
||||
case HOME:
|
||||
mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
|
||||
// Notify swipe-to-home (recents animation) is finished
|
||||
SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
|
||||
LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
|
||||
break;
|
||||
case RECENTS:
|
||||
mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
|
||||
@@ -891,19 +1001,29 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
mStateCallback.setState(STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT);
|
||||
break;
|
||||
case LAST_TASK:
|
||||
mStateCallback.setState(STATE_RESUME_LAST_TASK);
|
||||
if (postResumeLastTask != null) {
|
||||
ViewUtils.postFrameDrawn(postResumeLastTask,
|
||||
() -> mStateCallback.setState(STATE_RESUME_LAST_TASK));
|
||||
} else {
|
||||
mStateCallback.setState(STATE_RESUME_LAST_TASK);
|
||||
}
|
||||
if (mRecentsAnimationTargets != null) {
|
||||
TaskViewUtils.setSplitAuxiliarySurfacesShown(mRecentsAnimationTargets.nonApps,
|
||||
true /*shown*/, false /*animate*/);
|
||||
}
|
||||
break;
|
||||
}
|
||||
ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + endTarget);
|
||||
}
|
||||
|
||||
/** @return Whether this was the task we were waiting to appear, and thus handled it. */
|
||||
protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
|
||||
protected boolean handleTaskAppeared(RemoteAnimationTargetCompat[] appearedTaskTarget) {
|
||||
if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
|
||||
return false;
|
||||
}
|
||||
if (mStateCallback.hasStates(STATE_START_NEW_TASK)
|
||||
&& appearedTaskTarget.taskId == mGestureState.getLastStartedTaskId()) {
|
||||
boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTarget).anyMatch(
|
||||
targetCompat -> targetCompat.taskId == mGestureState.getLastStartedTaskId());
|
||||
if (mStateCallback.hasStates(STATE_START_NEW_TASK) && hasStartedTaskBefore) {
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
@@ -984,6 +1104,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
isFling, isCancel);
|
||||
// Set the state, but don't notify until the animation completes
|
||||
mGestureState.setEndTarget(endTarget, false /* isAtomic */);
|
||||
mAnimationFactory.setEndTarget(endTarget);
|
||||
|
||||
float endShift = endTarget.isLauncher ? 1 : 0;
|
||||
final float startShift;
|
||||
@@ -1028,9 +1149,6 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
if (mRecentsView != null) {
|
||||
int nearestPage = mRecentsView.getDestinationPage();
|
||||
boolean isScrolling = false;
|
||||
// Update page scroll before snapping to page to make sure we snapped to the
|
||||
// position calculated with target gesture in mind.
|
||||
mRecentsView.updateScrollSynchronously();
|
||||
if (mRecentsView.getNextPage() != nearestPage) {
|
||||
// We shouldn't really scroll to the next page when swiping up to recents.
|
||||
// Only allow settling on the next page if it's nearest to the center.
|
||||
@@ -1099,7 +1217,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
@UiThread
|
||||
private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
|
||||
GestureEndTarget target, PointF velocityPxPerMs) {
|
||||
runOnRecentsAnimationStart(() -> animateToProgressInternal(start, end, duration,
|
||||
runOnRecentsAnimationAndLauncherBound(() -> animateToProgressInternal(start, end, duration,
|
||||
interpolator, target, velocityPxPerMs));
|
||||
}
|
||||
|
||||
@@ -1136,8 +1254,15 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
mActivityRestartListener);
|
||||
|
||||
mParallelRunningAnim = mActivityInterface.getParallelAnimationToLauncher(
|
||||
mGestureState.getEndTarget(), duration);
|
||||
mGestureState.getEndTarget(), duration,
|
||||
mTaskAnimationManager.getCurrentCallbacks());
|
||||
if (mParallelRunningAnim != null) {
|
||||
mParallelRunningAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mParallelRunningAnim = null;
|
||||
}
|
||||
});
|
||||
mParallelRunningAnim.start();
|
||||
}
|
||||
}
|
||||
@@ -1154,22 +1279,25 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
boolean isTranslucent = runningTaskTarget != null && runningTaskTarget.isTranslucent;
|
||||
boolean appCanEnterPip = !mDeviceState.isPipActive()
|
||||
&& runningTaskTarget != null
|
||||
&& runningTaskTarget.taskInfo != null
|
||||
&& runningTaskTarget.allowEnterPip
|
||||
&& runningTaskTarget.taskInfo.pictureInPictureParams != null
|
||||
&& runningTaskTarget.taskInfo.pictureInPictureParams.isAutoEnterEnabled();
|
||||
HomeAnimationFactory homeAnimFactory =
|
||||
createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
|
||||
runningTaskTarget);
|
||||
mIsSwipingPipToHome = homeAnimFactory.supportSwipePipToHome() && appCanEnterPip;
|
||||
final RectFSpringAnim windowAnim;
|
||||
mIsSwipingPipToHome = !mIsSwipeForStagedSplit
|
||||
&& homeAnimFactory.supportSwipePipToHome() && appCanEnterPip;
|
||||
final RectFSpringAnim[] windowAnim;
|
||||
if (mIsSwipingPipToHome) {
|
||||
mSwipePipToHomeAnimator = createWindowAnimationToPip(
|
||||
homeAnimFactory, runningTaskTarget, start);
|
||||
windowAnim = mSwipePipToHomeAnimator;
|
||||
mSwipePipToHomeAnimators[0] = mSwipePipToHomeAnimator;
|
||||
windowAnim = mSwipePipToHomeAnimators;
|
||||
} else {
|
||||
mSwipePipToHomeAnimator = null;
|
||||
windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
|
||||
windowAnim.addAnimatorListener(new AnimationSuccessListener() {
|
||||
|
||||
windowAnim[0].addAnimatorListener(new AnimationSuccessListener() {
|
||||
@Override
|
||||
public void onAnimationSuccess(Animator animator) {
|
||||
if (mRecentsAnimationController == null) {
|
||||
@@ -1183,14 +1311,22 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
}
|
||||
});
|
||||
}
|
||||
windowAnim.start(mContext, velocityPxPerMs);
|
||||
mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
|
||||
mRunningWindowAnim = new RunningWindowAnim[windowAnim.length];
|
||||
for (int i = 0, windowAnimLength = windowAnim.length; i < windowAnimLength; i++) {
|
||||
RectFSpringAnim windowAnimation = windowAnim[i];
|
||||
if (windowAnimation == null) {
|
||||
continue;
|
||||
}
|
||||
windowAnimation.start(mContext, velocityPxPerMs);
|
||||
mRunningWindowAnim[i] = RunningWindowAnim.wrap(windowAnimation);
|
||||
}
|
||||
homeAnimFactory.setSwipeVelocity(velocityPxPerMs.y);
|
||||
homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
|
||||
mLauncherTransitionController = null;
|
||||
|
||||
if (mRecentsView != null) {
|
||||
mRecentsView.onPrepareGestureEndAnimation(null, mGestureState.getEndTarget());
|
||||
mRecentsView.onPrepareGestureEndAnimation(null, mGestureState.getEndTarget(),
|
||||
getRemoteTaskViewSimulators());
|
||||
}
|
||||
} else {
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
@@ -1232,24 +1368,42 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
animatorSet.play(windowAnim);
|
||||
if (mRecentsView != null) {
|
||||
mRecentsView.onPrepareGestureEndAnimation(
|
||||
animatorSet, mGestureState.getEndTarget());
|
||||
animatorSet, mGestureState.getEndTarget(),
|
||||
getRemoteTaskViewSimulators());
|
||||
}
|
||||
animatorSet.setDuration(duration).setInterpolator(interpolator);
|
||||
animatorSet.start();
|
||||
mRunningWindowAnim = RunningWindowAnim.wrap(animatorSet);
|
||||
mRunningWindowAnim = new RunningWindowAnim[]{RunningWindowAnim.wrap(animatorSet)};
|
||||
}
|
||||
}
|
||||
|
||||
private int calculateWindowRotation(RemoteAnimationTargetCompat runningTaskTarget,
|
||||
RecentsOrientedState orientationState) {
|
||||
if (runningTaskTarget.rotationChange != 0
|
||||
&& TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
|
||||
return Math.abs(runningTaskTarget.rotationChange) == ROTATION_90
|
||||
? ROTATION_270 : ROTATION_90;
|
||||
} else {
|
||||
return orientationState.getDisplayRotation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO(b/195473090) handle multiple task simulators (if needed) for PIP
|
||||
*/
|
||||
private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
|
||||
RemoteAnimationTargetCompat runningTaskTarget, float startProgress) {
|
||||
// Directly animate the app to PiP (picture-in-picture) mode
|
||||
final ActivityManager.RunningTaskInfo taskInfo = mGestureState.getRunningTask();
|
||||
final RecentsOrientedState orientationState = mTaskViewSimulator.getOrientationState();
|
||||
final int windowRotation = orientationState.getDisplayRotation();
|
||||
final RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
|
||||
.getOrientationState();
|
||||
final int windowRotation = calculateWindowRotation(runningTaskTarget, orientationState);
|
||||
final int homeRotation = orientationState.getRecentsActivityRotation();
|
||||
|
||||
final Matrix homeToWindowPositionMap = new Matrix();
|
||||
final RectF startRect = updateProgressForStartRect(homeToWindowPositionMap, startProgress);
|
||||
final Matrix[] homeToWindowPositionMaps = new Matrix[mRemoteTargetHandles.length];
|
||||
final RectF startRect = updateProgressForStartRect(homeToWindowPositionMaps,
|
||||
startProgress)[0];
|
||||
final Matrix homeToWindowPositionMap = homeToWindowPositionMaps[0];
|
||||
// Move the startRect to Launcher space as floatingIconView runs in Launcher
|
||||
final Matrix windowToHomePositionMap = new Matrix();
|
||||
homeToWindowPositionMap.invert(windowToHomePositionMap);
|
||||
@@ -1278,7 +1432,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
// is not ROTATION_0 (which implies the rotation is turned on in launcher settings).
|
||||
if (homeRotation == ROTATION_0
|
||||
&& (windowRotation == ROTATION_90 || windowRotation == ROTATION_270)) {
|
||||
builder.setFromRotation(mTaskViewSimulator, windowRotation,
|
||||
builder.setFromRotation(mRemoteTargetHandles[0].getTaskViewSimulator(), windowRotation,
|
||||
taskInfo.displayCutoutInsets);
|
||||
}
|
||||
final SwipePipToHomeAnimator swipePipToHomeAnimator = builder.build();
|
||||
@@ -1308,7 +1462,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
|
||||
}
|
||||
});
|
||||
setupWindowAnimation(swipePipToHomeAnimator);
|
||||
setupWindowAnimation(new RectFSpringAnim[]{swipePipToHomeAnimator});
|
||||
return swipePipToHomeAnimator;
|
||||
}
|
||||
|
||||
@@ -1335,19 +1489,19 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
* @param homeAnimationFactory The home animation factory.
|
||||
*/
|
||||
@Override
|
||||
protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
|
||||
protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress,
|
||||
HomeAnimationFactory homeAnimationFactory) {
|
||||
RectFSpringAnim anim =
|
||||
RectFSpringAnim[] anim =
|
||||
super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
|
||||
setupWindowAnimation(anim);
|
||||
return anim;
|
||||
}
|
||||
|
||||
private void setupWindowAnimation(RectFSpringAnim anim) {
|
||||
anim.addOnUpdateListener((v, r, p) -> {
|
||||
private void setupWindowAnimation(RectFSpringAnim[] anims) {
|
||||
anims[0].addOnUpdateListener((r, p) -> {
|
||||
updateSysUiFlags(Math.max(p, mCurrentShift.value));
|
||||
});
|
||||
anim.addAnimatorListener(new AnimationSuccessListener() {
|
||||
anims[0].addAnimatorListener(new AnimationSuccessListener() {
|
||||
@Override
|
||||
public void onAnimationSuccess(Animator animator) {
|
||||
if (mRecentsView != null) {
|
||||
@@ -1359,7 +1513,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
}
|
||||
});
|
||||
if (mRecentsAnimationTargets != null) {
|
||||
mRecentsAnimationTargets.addReleaseCheck(anim);
|
||||
mRecentsAnimationTargets.addReleaseCheck(anims[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1370,7 +1524,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
mActivity.clearRunOnceOnStartCallback();
|
||||
resetLauncherListeners();
|
||||
}
|
||||
if (mGestureState.getEndTarget() != null && !mGestureState.isRunningAnimationToLauncher()) {
|
||||
if (mGestureState.isRecentsAnimationRunning() && mGestureState.getEndTarget() != null
|
||||
&& !mGestureState.getEndTarget().isLauncher) {
|
||||
// Continued quick switch.
|
||||
cancelCurrentAnimation();
|
||||
} else {
|
||||
mStateCallback.setStateOnUiThread(STATE_FINISH_WITH_NO_END);
|
||||
@@ -1436,6 +1592,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
|
||||
private void reset() {
|
||||
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
|
||||
if (mActivity != null) {
|
||||
mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1513,6 +1672,11 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
|
||||
mActivityInterface.onTransitionCancelled(wasVisible, mGestureState.getEndTarget());
|
||||
|
||||
if (mRecentsAnimationTargets != null) {
|
||||
TaskViewUtils.setSplitAuxiliarySurfacesShown(mRecentsAnimationTargets.nonApps,
|
||||
true /*shown*/, false /*animate*/);
|
||||
}
|
||||
|
||||
// Leave the pending invisible flag, as it may be used by wallpaper open animation.
|
||||
if (mActivity != null) {
|
||||
mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
|
||||
@@ -1601,7 +1765,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
// If there are no targets or the animation not started, then there is nothing to finish
|
||||
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
|
||||
} else {
|
||||
maybeFinishSwipePipToHome();
|
||||
maybeFinishSwipeToHome();
|
||||
finishRecentsControllerToHome(
|
||||
() -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
|
||||
}
|
||||
@@ -1610,11 +1774,12 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the {@link #mIsSwipingPipToHome} and notifies SysUI that transition is finished
|
||||
* if applicable. This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
|
||||
* Notifies SysUI that transition is finished if applicable and also pass leash transactions
|
||||
* from Launcher to WM.
|
||||
* This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
|
||||
*/
|
||||
private void maybeFinishSwipePipToHome() {
|
||||
if (mIsSwipingPipToHome && mSwipePipToHomeAnimator != null) {
|
||||
private void maybeFinishSwipeToHome() {
|
||||
if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
|
||||
SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome(
|
||||
mSwipePipToHomeAnimator.getComponentName(),
|
||||
mSwipePipToHomeAnimator.getDestinationBounds(),
|
||||
@@ -1624,6 +1789,18 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
mSwipePipToHomeAnimator.getFinishTransaction(),
|
||||
mSwipePipToHomeAnimator.getContentOverlay());
|
||||
mIsSwipingPipToHome = false;
|
||||
} else if (mIsSwipeForStagedSplit) {
|
||||
// Transaction to hide the task to avoid flicker for entering PiP from split-screen.
|
||||
PictureInPictureSurfaceTransaction tx =
|
||||
new PictureInPictureSurfaceTransaction.Builder()
|
||||
.setAlpha(0f)
|
||||
.build();
|
||||
int[] taskIds =
|
||||
LauncherSplitScreenListener.INSTANCE.getNoCreate().getRunningSplitTaskIds();
|
||||
for (int taskId : taskIds) {
|
||||
mRecentsAnimationController.setFinishTaskTransaction(taskId,
|
||||
tx, null /* overlay */);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1636,7 +1813,10 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
endLauncherTransitionController();
|
||||
mRecentsView.onSwipeUpAnimationSuccess();
|
||||
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
|
||||
mTaskAnimationManager.setLiveTileCleanUpHandler(mInputConsumerProxy::destroy);
|
||||
mTaskAnimationManager.setLiveTileCleanUpHandler(() -> {
|
||||
mRecentsView.cleanupRemoteTargets();
|
||||
mInputConsumerProxy.destroy();
|
||||
});
|
||||
mTaskAnimationManager.enableLiveTileRestartListener();
|
||||
}
|
||||
|
||||
@@ -1655,8 +1835,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
* depend on proper class initialization.
|
||||
*/
|
||||
protected void initAfterSubclassConstructor() {
|
||||
initTransitionEndpoints(
|
||||
mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile());
|
||||
initTransitionEndpoints(mRemoteTargetHandles[0].getTaskViewSimulator()
|
||||
.getOrientationState().getLauncherDeviceProfile());
|
||||
}
|
||||
|
||||
protected void performHapticFeedback() {
|
||||
@@ -1673,13 +1853,14 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
|
||||
protected void linkRecentsViewScroll() {
|
||||
SurfaceTransactionApplier.create(mRecentsView, applier -> {
|
||||
mTransformParams.setSyncTransactionApplier(applier);
|
||||
runOnRecentsAnimationStart(() ->
|
||||
runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
|
||||
.setSyncTransactionApplier(applier));
|
||||
runOnRecentsAnimationAndLauncherBound(() ->
|
||||
mRecentsAnimationTargets.addReleaseCheck(applier));
|
||||
});
|
||||
|
||||
mRecentsView.addOnScrollChangedListener(mOnRecentsScrollListener);
|
||||
runOnRecentsAnimationStart(() ->
|
||||
runOnRecentsAnimationAndLauncherBound(() ->
|
||||
mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
|
||||
mRecentsAnimationTargets));
|
||||
mRecentsViewScrollLinked = true;
|
||||
@@ -1725,14 +1906,26 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given {@param action} if the recents animation has already started, or queues it to
|
||||
* be run when it is next started.
|
||||
* Runs the given {@param action} if the recents animation has already started and Launcher has
|
||||
* been created and bound to the TouchInteractionService, or queues it to be run when it this
|
||||
* next happens.
|
||||
*/
|
||||
protected void runOnRecentsAnimationStart(Runnable action) {
|
||||
if (mRecentsAnimationTargets == null) {
|
||||
mRecentsAnimationStartCallbacks.add(action);
|
||||
} else {
|
||||
action.run();
|
||||
private void runOnRecentsAnimationAndLauncherBound(Runnable action) {
|
||||
mRecentsAnimationStartCallbacks.add(action);
|
||||
flushOnRecentsAnimationAndLauncherBound();
|
||||
}
|
||||
|
||||
private void flushOnRecentsAnimationAndLauncherBound() {
|
||||
if (mRecentsAnimationTargets == null ||
|
||||
!mStateCallback.hasStates(STATE_LAUNCHER_BIND_TO_SERVICE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mRecentsAnimationStartCallbacks.isEmpty()) {
|
||||
for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
|
||||
action.run();
|
||||
}
|
||||
mRecentsAnimationStartCallbacks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1746,6 +1939,10 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
|
||||
@Override
|
||||
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
|
||||
if (!controller.getFinishTargetIsLauncher()) {
|
||||
TaskViewUtils.setSplitAuxiliarySurfacesShown(mRecentsAnimationTargets.nonApps,
|
||||
true /*shown*/, true /*animate*/);
|
||||
}
|
||||
mRecentsAnimationController = null;
|
||||
mRecentsAnimationTargets = null;
|
||||
if (mRecentsView != null) {
|
||||
@@ -1754,9 +1951,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
|
||||
public void onTasksAppeared(RemoteAnimationTargetCompat[] appearedTaskTargets) {
|
||||
if (mRecentsAnimationController != null) {
|
||||
if (handleTaskAppeared(appearedTaskTarget)) {
|
||||
if (handleTaskAppeared(appearedTaskTargets)) {
|
||||
mRecentsAnimationController.finish(false /* toRecents */,
|
||||
null /* onFinishComplete */);
|
||||
mActivityInterface.onLaunchTaskSuccess();
|
||||
@@ -1796,21 +1993,59 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
/**
|
||||
* Applies the transform on the recents animation
|
||||
*/
|
||||
protected void applyWindowTransform() {
|
||||
if (mWindowTransitionController != null) {
|
||||
mWindowTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor);
|
||||
}
|
||||
protected void applyScrollAndTransform() {
|
||||
// No need to apply any transform if there is ongoing swipe-pip-to-home animator since
|
||||
// that animator handles the leash solely.
|
||||
if (mRecentsAnimationTargets != null && !mIsSwipingPipToHome) {
|
||||
if (mRecentsViewScrollLinked && mRecentsView != null) {
|
||||
mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset());
|
||||
boolean notSwipingPipToHome = mRecentsAnimationTargets != null && !mIsSwipingPipToHome;
|
||||
boolean setRecentsScroll = mRecentsViewScrollLinked && mRecentsView != null;
|
||||
for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
|
||||
AnimatorControllerWithResistance playbackController =
|
||||
remoteHandle.getPlaybackController();
|
||||
if (playbackController != null) {
|
||||
playbackController.setProgress(Math.max(mCurrentShift.value,
|
||||
getScaleProgressDueToScroll()), mDragLengthFactor);
|
||||
}
|
||||
|
||||
if (notSwipingPipToHome) {
|
||||
TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator();
|
||||
if (setRecentsScroll) {
|
||||
taskViewSimulator.setScroll(mRecentsView.getScrollOffset());
|
||||
}
|
||||
taskViewSimulator.apply(remoteHandle.getTransformParams());
|
||||
}
|
||||
mTaskViewSimulator.apply(mTransformParams);
|
||||
}
|
||||
ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
|
||||
}
|
||||
|
||||
// Scaling of RecentsView during quick switch based on amount of recents scroll
|
||||
private float getScaleProgressDueToScroll() {
|
||||
if (mActivity == null || !mActivity.getDeviceProfile().isTablet || mRecentsView == null
|
||||
|| !mRecentsViewScrollLinked) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
float scrollOffset = Math.abs(mRecentsView.getScrollOffset(mRecentsView.getCurrentPage()));
|
||||
int maxScrollOffset = mRecentsView.getPagedOrientationHandler().getPrimaryValue(
|
||||
mRecentsView.getLastComputedTaskSize().width(),
|
||||
mRecentsView.getLastComputedTaskSize().height());
|
||||
maxScrollOffset += mRecentsView.getPageSpacing();
|
||||
|
||||
float maxScaleProgress =
|
||||
MAX_QUICK_SWITCH_RECENTS_SCALE_PROGRESS * mRecentsView.getMaxScaleForFullScreen();
|
||||
float scaleProgress = maxScaleProgress;
|
||||
|
||||
if (scrollOffset < mQuickSwitchScaleScrollThreshold) {
|
||||
scaleProgress = Utilities.mapToRange(scrollOffset, 0, mQuickSwitchScaleScrollThreshold,
|
||||
0, maxScaleProgress, ACCEL_DEACCEL);
|
||||
} else if (scrollOffset > (maxScrollOffset - mQuickSwitchScaleScrollThreshold)) {
|
||||
scaleProgress = Utilities.mapToRange(scrollOffset,
|
||||
(maxScrollOffset - mQuickSwitchScaleScrollThreshold), maxScrollOffset,
|
||||
maxScaleProgress, 0, ACCEL_DEACCEL);
|
||||
}
|
||||
|
||||
return scaleProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for winscope tracing, see launcher_trace.proto
|
||||
* @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
|
||||
@@ -1832,7 +2067,6 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
}
|
||||
|
||||
public interface Factory {
|
||||
|
||||
AbsSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,16 @@ public class AnimatedFloat {
|
||||
mUpdateCallback = updateCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an animation from the current value to the given value.
|
||||
*/
|
||||
public ObjectAnimator animateToValue(float end) {
|
||||
return animateToValue(value, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an animation from the given start value to the given end value.
|
||||
*/
|
||||
public ObjectAnimator animateToValue(float start, float end) {
|
||||
cancelAnimation();
|
||||
mValueAnimator = ObjectAnimator.ofFloat(this, VALUE, start, end);
|
||||
|
||||
@@ -30,6 +30,7 @@ import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
|
||||
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
@@ -40,6 +41,7 @@ import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
@@ -48,10 +50,10 @@ import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.statehandlers.DepthController;
|
||||
import com.android.launcher3.statemanager.BaseState;
|
||||
import com.android.launcher3.statemanager.StatefulActivity;
|
||||
import com.android.launcher3.taskbar.TaskbarUIController;
|
||||
import com.android.launcher3.touch.PagedOrientationHandler;
|
||||
import com.android.launcher3.util.WindowBounds;
|
||||
import com.android.launcher3.views.ScrimView;
|
||||
@@ -65,6 +67,7 @@ import com.android.quickstep.views.TaskView;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -77,12 +80,14 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
|
||||
public final boolean rotationSupportedByActivity;
|
||||
|
||||
private final STATE_TYPE mOverviewState, mBackgroundState;
|
||||
private final STATE_TYPE mBackgroundState;
|
||||
|
||||
private STATE_TYPE mTargetState;
|
||||
|
||||
protected BaseActivityInterface(boolean rotationSupportedByActivity,
|
||||
STATE_TYPE overviewState, STATE_TYPE backgroundState) {
|
||||
this.rotationSupportedByActivity = rotationSupportedByActivity;
|
||||
mOverviewState = overviewState;
|
||||
mTargetState = overviewState;
|
||||
mBackgroundState = backgroundState;
|
||||
}
|
||||
|
||||
@@ -136,6 +141,9 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public abstract TaskbarUIController getTaskbarController();
|
||||
|
||||
public final boolean isResumed() {
|
||||
ACTIVITY_TYPE activity = getCreatedActivity();
|
||||
return activity != null && activity.hasBeenResumed();
|
||||
@@ -186,7 +194,8 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
|
||||
public void closeOverlay() { }
|
||||
|
||||
public void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData, Runnable runnable) {
|
||||
public void switchRunningTaskViewToScreenshot(HashMap<Integer, ThumbnailData> thumbnailDatas,
|
||||
Runnable runnable) {
|
||||
ACTIVITY_TYPE activity = getCreatedActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
@@ -198,46 +207,31 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
}
|
||||
return;
|
||||
}
|
||||
recentsView.switchToScreenshot(thumbnailData, runnable);
|
||||
recentsView.switchToScreenshot(thumbnailDatas, runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the taskView size for the provided device configuration.
|
||||
*/
|
||||
public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
|
||||
PagedOrientationHandler orientedState) {
|
||||
public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect) {
|
||||
Resources res = context.getResources();
|
||||
if (dp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
|
||||
if (dp.overviewShowAsGrid) {
|
||||
Rect gridRect = new Rect();
|
||||
calculateGridSize(context, dp, gridRect);
|
||||
|
||||
int verticalMargin = res.getDimensionPixelSize(
|
||||
R.dimen.overview_grid_focus_vertical_margin);
|
||||
float taskHeight = gridRect.height() - verticalMargin * 2;
|
||||
|
||||
PointF taskDimension = getTaskDimension(context, dp);
|
||||
float scale = taskHeight / Math.max(taskDimension.x, taskDimension.y);
|
||||
float scale = gridRect.height() / taskDimension.y;
|
||||
scale = Math.min(scale, res.getFloat(R.dimen.overview_max_scale));
|
||||
int outWidth = Math.round(scale * taskDimension.x);
|
||||
int outHeight = Math.round(scale * taskDimension.y);
|
||||
|
||||
int gravity = Gravity.CENTER_VERTICAL;
|
||||
gravity |= orientedState.getRecentsRtlSetting(res) ? Gravity.RIGHT : Gravity.LEFT;
|
||||
int gravity = Gravity.CENTER;
|
||||
Gravity.apply(gravity, outWidth, outHeight, gridRect, outRect);
|
||||
} else {
|
||||
int taskMargin = dp.overviewTaskMarginPx;
|
||||
int proactiveRowAndMargin;
|
||||
if (!TaskView.SHOW_PROACTIVE_ACTIONS || dp.isVerticalBarLayout()) {
|
||||
// In Vertical Bar Layout the proactive row doesn't have its own space, it's inside
|
||||
// the actions row.
|
||||
proactiveRowAndMargin = 0;
|
||||
} else {
|
||||
proactiveRowAndMargin = res.getDimensionPixelSize(
|
||||
R.dimen.overview_proactive_row_height)
|
||||
+ res.getDimensionPixelSize(R.dimen.overview_proactive_row_bottom_margin);
|
||||
}
|
||||
calculateTaskSizeInternal(context, dp,
|
||||
dp.overviewTaskThumbnailTopMarginPx,
|
||||
proactiveRowAndMargin + getOverviewActionsHeight(context, dp),
|
||||
getOverviewActionsHeight(context, dp),
|
||||
res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size) + taskMargin,
|
||||
outRect);
|
||||
}
|
||||
@@ -278,19 +272,35 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
public static void getTaskDimension(Context context, DeviceProfile dp, PointF out) {
|
||||
if (dp.isMultiWindowMode) {
|
||||
WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(context);
|
||||
if (TaskView.CLIP_STATUS_AND_NAV_BARS) {
|
||||
out.x = bounds.availableSize.x;
|
||||
out.y = bounds.availableSize.y;
|
||||
} else {
|
||||
out.x = bounds.availableSize.x + bounds.insets.left + bounds.insets.right;
|
||||
out.y = bounds.availableSize.y + bounds.insets.top + bounds.insets.bottom;
|
||||
out.x = bounds.availableSize.x;
|
||||
out.y = bounds.availableSize.y;
|
||||
if (!TaskView.clipLeft(dp)) {
|
||||
out.x += bounds.insets.left;
|
||||
}
|
||||
if (!TaskView.clipRight(dp)) {
|
||||
out.x += bounds.insets.right;
|
||||
}
|
||||
if (!TaskView.clipTop(dp)) {
|
||||
out.y += bounds.insets.top;
|
||||
}
|
||||
if (!TaskView.clipBottom(dp)) {
|
||||
out.y += bounds.insets.bottom;
|
||||
}
|
||||
} else if (TaskView.CLIP_STATUS_AND_NAV_BARS) {
|
||||
out.x = dp.availableWidthPx;
|
||||
out.y = dp.availableHeightPx;
|
||||
} else {
|
||||
out.x = dp.widthPx;
|
||||
out.y = dp.heightPx;
|
||||
if (TaskView.clipLeft(dp)) {
|
||||
out.x -= dp.getInsets().left;
|
||||
}
|
||||
if (TaskView.clipRight(dp)) {
|
||||
out.x -= dp.getInsets().right;
|
||||
}
|
||||
if (TaskView.clipTop(dp)) {
|
||||
out.y -= dp.getInsets().top;
|
||||
}
|
||||
if (TaskView.clipBottom(dp)) {
|
||||
out.y -= Math.max(dp.getInsets().bottom, dp.taskbarSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,14 +308,13 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
* Calculates the overview grid size for the provided device configuration.
|
||||
*/
|
||||
public final void calculateGridSize(Context context, DeviceProfile dp, Rect outRect) {
|
||||
Resources res = context.getResources();
|
||||
int topMargin = res.getDimensionPixelSize(R.dimen.overview_grid_top_margin);
|
||||
int bottomMargin = res.getDimensionPixelSize(R.dimen.overview_grid_bottom_margin);
|
||||
int sideMargin = res.getDimensionPixelSize(R.dimen.overview_grid_side_margin);
|
||||
|
||||
Rect insets = dp.getInsets();
|
||||
int topMargin = dp.overviewTaskThumbnailTopMarginPx;
|
||||
int bottomMargin = getOverviewActionsHeight(context, dp);
|
||||
int sideMargin = dp.overviewGridSideMargin;
|
||||
|
||||
outRect.set(0, 0, dp.widthPx, dp.heightPx);
|
||||
outRect.inset(Math.max(insets.left, sideMargin), Math.max(insets.top, topMargin),
|
||||
outRect.inset(Math.max(insets.left, sideMargin), insets.top + topMargin,
|
||||
Math.max(insets.right, sideMargin), Math.max(insets.bottom, bottomMargin));
|
||||
}
|
||||
|
||||
@@ -315,22 +324,21 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
public final void calculateGridTaskSize(Context context, DeviceProfile dp, Rect outRect,
|
||||
PagedOrientationHandler orientedState) {
|
||||
Resources res = context.getResources();
|
||||
Rect gridRect = new Rect();
|
||||
calculateGridSize(context, dp, gridRect);
|
||||
Rect taskRect = new Rect();
|
||||
calculateTaskSize(context, dp, taskRect);
|
||||
|
||||
int rowSpacing = res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing);
|
||||
float rowHeight = (gridRect.height() - rowSpacing) / 2f;
|
||||
float rowHeight =
|
||||
(taskRect.height() + dp.overviewTaskThumbnailTopMarginPx - dp.overviewRowSpacing)
|
||||
/ 2f;
|
||||
|
||||
PointF taskDimension = getTaskDimension(context, dp);
|
||||
float scale = (rowHeight - dp.overviewTaskThumbnailTopMarginPx) / Math.max(
|
||||
taskDimension.x, taskDimension.y);
|
||||
float scale = (rowHeight - dp.overviewTaskThumbnailTopMarginPx) / taskDimension.y;
|
||||
int outWidth = Math.round(scale * taskDimension.x);
|
||||
int outHeight = Math.round(scale * taskDimension.y);
|
||||
|
||||
int gravity = Gravity.TOP;
|
||||
gravity |= orientedState.getRecentsRtlSetting(res) ? Gravity.RIGHT : Gravity.LEFT;
|
||||
gridRect.inset(0, dp.overviewTaskThumbnailTopMarginPx, 0, 0);
|
||||
Gravity.apply(gravity, outWidth, outHeight, gridRect, outRect);
|
||||
Gravity.apply(gravity, outWidth, outHeight, taskRect, outRect);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -358,7 +366,8 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
* an optional additional animation with the same duration.
|
||||
*/
|
||||
public @Nullable Animator getParallelAnimationToLauncher(
|
||||
GestureState.GestureEndTarget endTarget, long duration) {
|
||||
GestureState.GestureEndTarget endTarget, long duration,
|
||||
RecentsAnimationCallbacks callbacks) {
|
||||
if (endTarget == RECENTS) {
|
||||
ACTIVITY_TYPE activity = getCreatedActivity();
|
||||
if (activity == null) {
|
||||
@@ -385,6 +394,20 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
*/
|
||||
public abstract STATE_TYPE stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget);
|
||||
|
||||
/**
|
||||
* Called when the animation to the target has finished, but right before updating the state.
|
||||
* @return A View that needs to draw before ending the recents animation to LAST_TASK.
|
||||
* (This is a hack to ensure Taskbar draws its background first to avoid flickering.)
|
||||
*/
|
||||
public @Nullable View onSettledOnEndTarget(GestureState.GestureEndTarget endTarget) {
|
||||
TaskbarUIController taskbarUIController = getTaskbarController();
|
||||
if (taskbarUIController != null) {
|
||||
taskbarUIController.setSystemGestureInProgress(false);
|
||||
return taskbarUIController.getRootView();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public interface AnimationFactory {
|
||||
|
||||
void createActivityInterface(long transitionLength);
|
||||
@@ -399,6 +422,13 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
default boolean isRecentsAttachedToAppWindow() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean hasRecentsEverAttachedToAppWindow() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Called when the gesture ends and we know what state it is going towards */
|
||||
default void setEndTarget(GestureState.GestureEndTarget endTarget) { }
|
||||
}
|
||||
|
||||
class DefaultAnimationFactory implements AnimationFactory {
|
||||
@@ -408,6 +438,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
private final Consumer<AnimatorControllerWithResistance> mCallback;
|
||||
|
||||
private boolean mIsAttachedToWindow;
|
||||
private boolean mHasEverAttachedToWindow;
|
||||
|
||||
DefaultAnimationFactory(Consumer<AnimatorControllerWithResistance> callback) {
|
||||
mCallback = callback;
|
||||
@@ -435,7 +466,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
|
||||
// Since we are changing the start position of the UI, reapply the state, at the end
|
||||
controller.setEndAction(() -> mActivity.getStateManager().goToState(
|
||||
controller.getInterpolatedProgress() > 0.5 ? mOverviewState : mBackgroundState,
|
||||
controller.getInterpolatedProgress() > 0.5 ? mTargetState : mBackgroundState,
|
||||
false));
|
||||
|
||||
RecentsView recentsView = mActivity.getOverviewPanel();
|
||||
@@ -461,6 +492,9 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
}
|
||||
mIsAttachedToWindow = attached;
|
||||
RecentsView recentsView = mActivity.getOverviewPanel();
|
||||
if (attached) {
|
||||
mHasEverAttachedToWindow = true;
|
||||
}
|
||||
Animator fadeAnim = mActivity.getStateManager()
|
||||
.createStateElementAnimation(INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
|
||||
|
||||
@@ -490,17 +524,32 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
return mIsAttachedToWindow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRecentsEverAttachedToAppWindow() {
|
||||
return mHasEverAttachedToWindow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEndTarget(GestureState.GestureEndTarget endTarget) {
|
||||
mTargetState = stateFromGestureEndTarget(endTarget);
|
||||
}
|
||||
|
||||
protected void createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa) {
|
||||
// Scale down recents from being full screen to being in overview.
|
||||
RecentsView recentsView = activity.getOverviewPanel();
|
||||
pa.addFloat(recentsView, RECENTS_SCALE_PROPERTY,
|
||||
recentsView.getMaxScaleForFullScreen(), 1, LINEAR);
|
||||
pa.addFloat(recentsView, FULLSCREEN_PROGRESS, 1, 0, LINEAR);
|
||||
|
||||
pa.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
TaskbarUIController taskbarUIController = getTaskbarController();
|
||||
if (taskbarUIController != null) {
|
||||
taskbarUIController.setSystemGestureInProgress(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when OverviewService is bound to this process */
|
||||
void onOverviewServiceBound() {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import static com.android.quickstep.fallback.RecentsState.BACKGROUND_APP;
|
||||
import static com.android.quickstep.fallback.RecentsState.DEFAULT;
|
||||
import static com.android.quickstep.fallback.RecentsState.HOME;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.view.MotionEvent;
|
||||
@@ -29,7 +31,9 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.statemanager.StateManager;
|
||||
import com.android.launcher3.taskbar.FallbackTaskbarUIController;
|
||||
import com.android.launcher3.touch.PagedOrientationHandler;
|
||||
import com.android.quickstep.GestureState.GestureEndTarget;
|
||||
import com.android.quickstep.fallback.RecentsState;
|
||||
import com.android.quickstep.util.ActivityInitListener;
|
||||
import com.android.quickstep.util.AnimatorControllerWithResistance;
|
||||
@@ -57,7 +61,7 @@ public final class FallbackActivityInterface extends
|
||||
@Override
|
||||
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
|
||||
PagedOrientationHandler orientationHandler) {
|
||||
calculateTaskSize(context, dp, outRect, orientationHandler);
|
||||
calculateTaskSize(context, dp, outRect);
|
||||
if (dp.isVerticalBarLayout()
|
||||
&& SysUINavigationMode.INSTANCE.get(context).getMode() != NO_BUTTON) {
|
||||
return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right);
|
||||
@@ -102,6 +106,15 @@ public final class FallbackActivityInterface extends
|
||||
return RecentsActivity.ACTIVITY_TRACKER.getCreatedActivity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FallbackTaskbarUIController getTaskbarController() {
|
||||
RecentsActivity activity = getCreatedActivity();
|
||||
if (activity == null) {
|
||||
return null;
|
||||
}
|
||||
return activity.getTaskbarUIController();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public RecentsView getVisibleRecentsView() {
|
||||
@@ -182,7 +195,7 @@ public final class FallbackActivityInterface extends
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecentsState stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget) {
|
||||
public RecentsState stateFromGestureEndTarget(GestureEndTarget endTarget) {
|
||||
switch (endTarget) {
|
||||
case RECENTS:
|
||||
return DEFAULT;
|
||||
@@ -202,6 +215,28 @@ public final class FallbackActivityInterface extends
|
||||
rotationTouchHelper.getDisplayRotation());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Animator getParallelAnimationToLauncher(GestureEndTarget endTarget,
|
||||
long duration, RecentsAnimationCallbacks callbacks) {
|
||||
FallbackTaskbarUIController uiController = getTaskbarController();
|
||||
Animator superAnimator = super.getParallelAnimationToLauncher(
|
||||
endTarget, duration, callbacks);
|
||||
if (uiController == null) {
|
||||
return superAnimator;
|
||||
}
|
||||
RecentsState toState = stateFromGestureEndTarget(endTarget);
|
||||
Animator taskbarAnimator = uiController.createAnimToRecentsState(toState, duration);
|
||||
if (taskbarAnimator == null) {
|
||||
return superAnimator;
|
||||
}
|
||||
if (superAnimator == null) {
|
||||
return taskbarAnimator;
|
||||
}
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
animatorSet.playTogether(superAnimator, taskbarAnimator);
|
||||
return animatorSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getOverviewScrimColorForState(RecentsActivity activity, RecentsState state) {
|
||||
return state.getScrimColor(activity);
|
||||
|
||||
@@ -28,6 +28,7 @@ import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACT
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityOptions;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
@@ -49,7 +50,6 @@ import android.view.SurfaceControl;
|
||||
import android.view.SurfaceControl.Transaction;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Utilities;
|
||||
@@ -58,7 +58,6 @@ import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.anim.SpringAnimationBuilder;
|
||||
import com.android.quickstep.fallback.FallbackRecentsView;
|
||||
import com.android.quickstep.fallback.RecentsState;
|
||||
import com.android.quickstep.util.AppCloseConfig;
|
||||
import com.android.quickstep.util.RectFSpringAnim;
|
||||
import com.android.quickstep.util.TransformParams;
|
||||
import com.android.quickstep.util.TransformParams.BuilderProxy;
|
||||
@@ -101,7 +100,9 @@ public class FallbackSwipeHandler extends
|
||||
|
||||
mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask());
|
||||
if (mRunningOverHome) {
|
||||
mTransformParams.setHomeBuilderProxy(this::updateHomeActivityTransformDuringSwipeUp);
|
||||
runActionOnRemoteHandles(remoteTargetHandle ->
|
||||
remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
|
||||
FallbackSwipeHandler.this::updateHomeActivityTransformDuringSwipeUp));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +110,9 @@ public class FallbackSwipeHandler extends
|
||||
protected void initTransitionEndpoints(DeviceProfile dp) {
|
||||
super.initTransitionEndpoints(dp);
|
||||
if (mRunningOverHome) {
|
||||
mMaxLauncherScale = 1 / mTaskViewSimulator.getFullScreenScale();
|
||||
// Full screen scale should be independent of remote target handle
|
||||
mMaxLauncherScale = 1 / mRemoteTargetHandles[0].getTaskViewSimulator()
|
||||
.getFullScreenScale();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +147,7 @@ public class FallbackSwipeHandler extends
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
|
||||
protected boolean handleTaskAppeared(RemoteAnimationTargetCompat[] appearedTaskTarget) {
|
||||
if (mActiveAnimationFactory != null
|
||||
&& mActiveAnimationFactory.handleHomeTaskAppeared(appearedTaskTarget)) {
|
||||
mActiveAnimationFactory = null;
|
||||
@@ -173,7 +176,10 @@ public class FallbackSwipeHandler extends
|
||||
@Override
|
||||
protected void notifyGestureAnimationStartToRecents() {
|
||||
if (mRunningOverHome) {
|
||||
mRecentsView.onGestureAnimationStartOnHome(mGestureState.getRunningTask());
|
||||
if (SysUINavigationMode.getMode(mContext).hasGestures) {
|
||||
mRecentsView.onGestureAnimationStartOnHome(
|
||||
new ActivityManager.RunningTaskInfo[]{mGestureState.getRunningTask()});
|
||||
}
|
||||
} else {
|
||||
super.notifyGestureAnimationStartToRecents();
|
||||
}
|
||||
@@ -200,19 +206,24 @@ public class FallbackSwipeHandler extends
|
||||
mHomeAlpha = new AnimatedFloat();
|
||||
mHomeAlpha.value = Utilities.boundToRange(1 - mCurrentShift.value, 0, 1);
|
||||
mVerticalShiftForScale.value = mCurrentShift.value;
|
||||
mTransformParams.setHomeBuilderProxy(
|
||||
this::updateHomeActivityTransformDuringHomeAnim);
|
||||
runActionOnRemoteHandles(remoteTargetHandle ->
|
||||
remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
|
||||
FallbackHomeAnimationFactory.this
|
||||
::updateHomeActivityTransformDuringHomeAnim));
|
||||
} else {
|
||||
mHomeAlpha = new AnimatedFloat(this::updateHomeAlpha);
|
||||
mHomeAlpha.value = 0;
|
||||
|
||||
mHomeAlphaParams.setHomeBuilderProxy(
|
||||
this::updateHomeActivityTransformDuringHomeAnim);
|
||||
runActionOnRemoteHandles(remoteTargetHandle ->
|
||||
remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
|
||||
FallbackHomeAnimationFactory.this
|
||||
::updateHomeActivityTransformDuringHomeAnim));
|
||||
}
|
||||
|
||||
mRecentsAlpha.value = 1;
|
||||
mTransformParams.setBaseBuilderProxy(
|
||||
this::updateRecentsActivityTransformDuringHomeAnim);
|
||||
runActionOnRemoteHandles(remoteTargetHandle ->
|
||||
remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
|
||||
FallbackHomeAnimationFactory.this
|
||||
::updateRecentsActivityTransformDuringHomeAnim));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -249,7 +260,8 @@ public class FallbackSwipeHandler extends
|
||||
}
|
||||
}
|
||||
|
||||
public boolean handleHomeTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
|
||||
public boolean handleHomeTaskAppeared(RemoteAnimationTargetCompat[] appearedTaskTargets) {
|
||||
RemoteAnimationTargetCompat appearedTaskTarget = appearedTaskTargets[0];
|
||||
if (appearedTaskTarget.activityType == ACTIVITY_TYPE_HOME) {
|
||||
RemoteAnimationTargets targets = new RemoteAnimationTargets(
|
||||
new RemoteAnimationTargetCompat[] {appearedTaskTarget},
|
||||
@@ -304,8 +316,7 @@ public class FallbackSwipeHandler extends
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@Nullable AppCloseConfig config, RectF currentRect, float progress,
|
||||
float radius) {
|
||||
public void update(RectF currentRect, float progress, float radius) {
|
||||
if (mSurfaceControl != null) {
|
||||
currentRect.roundOut(mTempRect);
|
||||
Transaction t = new Transaction();
|
||||
|
||||
@@ -20,6 +20,7 @@ import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
|
||||
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Intent;
|
||||
@@ -35,6 +36,7 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -122,10 +124,6 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
public static final int STATE_RECENTS_ANIMATION_ENDED =
|
||||
getFlagForIndex("STATE_RECENTS_ANIMATION_ENDED");
|
||||
|
||||
// Called when we create an overscroll window when swiping right to left on the most recent app
|
||||
public static final int STATE_OVERSCROLL_WINDOW_CREATED =
|
||||
getFlagForIndex("STATE_OVERSCROLL_WINDOW_CREATED");
|
||||
|
||||
// Called when RecentsView stops scrolling and settles on a TaskView.
|
||||
public static final int STATE_RECENTS_SCROLLING_FINISHED =
|
||||
getFlagForIndex("STATE_RECENTS_SCROLLING_FINISHED");
|
||||
@@ -138,12 +136,13 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
private final int mGestureId;
|
||||
|
||||
private ActivityManager.RunningTaskInfo mRunningTask;
|
||||
private ActivityManager.RunningTaskInfo[] mRunningTasks;
|
||||
private GestureEndTarget mEndTarget;
|
||||
private RemoteAnimationTargetCompat mLastAppearedTaskTarget;
|
||||
private Set<Integer> mPreviouslyAppearedTaskIds = new HashSet<>();
|
||||
private int mLastStartedTaskId = -1;
|
||||
private RecentsAnimationController mRecentsAnimationController;
|
||||
private ThumbnailData mRecentsAnimationCanceledSnapshot;
|
||||
private HashMap<Integer, ThumbnailData> mRecentsAnimationCanceledSnapshots;
|
||||
|
||||
/** The time when the swipe up gesture is triggered. */
|
||||
private long mSwipeUpStartTimeMs;
|
||||
@@ -237,6 +236,14 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
return mRunningTask;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will array will contain the task returned by {@link #getRunningTask()}
|
||||
* @return the running tasks for this gesture.
|
||||
*/
|
||||
public ActivityManager.RunningTaskInfo[] getRunningTasks() {
|
||||
return mRunningTasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the running task id for this gesture.
|
||||
*/
|
||||
@@ -251,6 +258,15 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
mRunningTask = runningTask;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO(b/210903248) refactor to consolidate w/ method above
|
||||
* Updates the running task for the gesture to be the given {@param runningTask}.
|
||||
*/
|
||||
public void updateRunningTasks(ActivityManager.RunningTaskInfo[] runningTasks) {
|
||||
mRunningTasks = runningTasks;
|
||||
updateRunningTask(runningTasks[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last task that appeared during this gesture.
|
||||
*/
|
||||
@@ -346,8 +362,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
* @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);
|
||||
return mStateCallback.hasStates(STATE_RECENTS_ANIMATION_STARTED)
|
||||
&& !mStateCallback.hasStates(STATE_RECENTS_ANIMATION_ENDED);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -358,16 +374,16 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
|
||||
mRecentsAnimationCanceledSnapshot = thumbnailData;
|
||||
public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
|
||||
mRecentsAnimationCanceledSnapshots = thumbnailDatas;
|
||||
mStateCallback.setState(STATE_RECENTS_ANIMATION_CANCELED);
|
||||
mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED);
|
||||
if (mRecentsAnimationCanceledSnapshot != null) {
|
||||
if (mRecentsAnimationCanceledSnapshots != null) {
|
||||
// Clean up the screenshot to finalize the recents animation cancel
|
||||
if (mRecentsAnimationController != null) {
|
||||
mRecentsAnimationController.cleanupScreenshot();
|
||||
}
|
||||
mRecentsAnimationCanceledSnapshot = null;
|
||||
mRecentsAnimationCanceledSnapshots = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,10 +398,15 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
* while STATE_RECENTS_ANIMATION_CANCELED state is being set, and the caller is responsible for
|
||||
* calling {@link RecentsAnimationController#cleanupScreenshot()}.
|
||||
*/
|
||||
ThumbnailData consumeRecentsAnimationCanceledSnapshot() {
|
||||
ThumbnailData data = mRecentsAnimationCanceledSnapshot;
|
||||
mRecentsAnimationCanceledSnapshot = null;
|
||||
return data;
|
||||
@Nullable
|
||||
HashMap<Integer, ThumbnailData> consumeRecentsAnimationCanceledSnapshot() {
|
||||
if (mRecentsAnimationCanceledSnapshots != null) {
|
||||
HashMap<Integer, ThumbnailData> data =
|
||||
new HashMap<Integer, ThumbnailData>(mRecentsAnimationCanceledSnapshots);
|
||||
mRecentsAnimationCanceledSnapshots = null;
|
||||
return data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void setSwipeUpStartTimeMs(long uptimeMs) {
|
||||
|
||||
@@ -36,9 +36,10 @@ public interface InputConsumer {
|
||||
int TYPE_SCREEN_PINNED = 1 << 6;
|
||||
int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7;
|
||||
int TYPE_RESET_GESTURE = 1 << 8;
|
||||
int TYPE_OVERSCROLL = 1 << 9;
|
||||
int TYPE_PROGRESS_DELEGATE = 1 << 9;
|
||||
int TYPE_SYSUI_OVERLAY = 1 << 10;
|
||||
int TYPE_ONE_HANDED = 1 << 11;
|
||||
int TYPE_TASKBAR_STASH = 1 << 12;
|
||||
|
||||
String[] NAMES = new String[] {
|
||||
"TYPE_NO_OP", // 0
|
||||
@@ -50,9 +51,10 @@ public interface InputConsumer {
|
||||
"TYPE_SCREEN_PINNED", // 6
|
||||
"TYPE_OVERVIEW_WITHOUT_FOCUS", // 7
|
||||
"TYPE_RESET_GESTURE", // 8
|
||||
"TYPE_OVERSCROLL", // 9
|
||||
"TYPE_PROGRESS_DELEGATE", // 9
|
||||
"TYPE_SYSUI_OVERLAY", // 10
|
||||
"TYPE_ONE_HANDED", // 11
|
||||
"TYPE_TASKBAR_STASH", // 12
|
||||
};
|
||||
|
||||
InputConsumer NO_OP = () -> TYPE_NO_OP;
|
||||
@@ -97,6 +99,8 @@ public interface InputConsumer {
|
||||
|
||||
default void onMotionEvent(MotionEvent ev) { }
|
||||
|
||||
default void onHoverEvent(MotionEvent ev) { }
|
||||
|
||||
default void onKeyEvent(KeyEvent ev) { }
|
||||
|
||||
default void onInputEvent(InputEvent ev) {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.quickstep;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
|
||||
/**
|
||||
* Bridge class to allow using resources in Kotlin.
|
||||
* <br/>
|
||||
* TODO(b/204069723) Can't use resources directly in Kotlin
|
||||
*/
|
||||
public class KtR {
|
||||
public static final class id {
|
||||
public static int menu_option_layout = R.id.menu_option_layout;
|
||||
}
|
||||
|
||||
public static final class dimen {
|
||||
public static int task_menu_spacing = R.dimen.task_menu_spacing;
|
||||
public static int task_menu_horizontal_padding = R.dimen.task_menu_horizontal_padding;
|
||||
}
|
||||
|
||||
public static final class layout {
|
||||
public static int task_menu_with_arrow = R.layout.task_menu_with_arrow;
|
||||
public static int task_view_menu_option = R.layout.task_view_menu_option;
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ public final class LauncherActivityInterface extends
|
||||
@Override
|
||||
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
|
||||
PagedOrientationHandler orientationHandler) {
|
||||
calculateTaskSize(context, dp, outRect, orientationHandler);
|
||||
calculateTaskSize(context, dp, outRect);
|
||||
if (dp.isVerticalBarLayout() && SysUINavigationMode.getMode(context) != Mode.NO_BUTTON) {
|
||||
return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right);
|
||||
} else {
|
||||
@@ -130,7 +130,6 @@ public final class LauncherActivityInterface extends
|
||||
pa.addFloat(getDepthController(),
|
||||
new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
|
||||
fromDepthRatio, toDepthRatio, LINEAR);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@@ -173,7 +172,8 @@ public final class LauncherActivityInterface extends
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private LauncherTaskbarUIController getTaskbarController() {
|
||||
@Override
|
||||
public LauncherTaskbarUIController getTaskbarController() {
|
||||
BaseQuickstepLauncher launcher = getCreatedActivity();
|
||||
if (launcher == null) {
|
||||
return null;
|
||||
@@ -189,7 +189,7 @@ public final class LauncherActivityInterface extends
|
||||
launcher != null && launcher.getStateManager().getState().overviewUi
|
||||
? launcher.getOverviewPanel() : null;
|
||||
if (recentsView == null || (!launcher.hasBeenResumed()
|
||||
&& recentsView.getRunningTaskId() == -1)) {
|
||||
&& recentsView.getRunningTaskViewId() == -1)) {
|
||||
// If live tile has ended, return null.
|
||||
return null;
|
||||
}
|
||||
@@ -292,25 +292,23 @@ public final class LauncherActivityInterface extends
|
||||
} else {
|
||||
om.hideOverlay(150);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void onOverviewServiceBound() {
|
||||
final BaseQuickstepLauncher activity = getCreatedActivity();
|
||||
if (activity == null) return;
|
||||
activity.getAppTransitionManager().registerRemoteTransitions();
|
||||
LauncherTaskbarUIController taskbarController = getTaskbarController();
|
||||
if (taskbarController != null) {
|
||||
taskbarController.hideEdu();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Animator getParallelAnimationToLauncher(GestureEndTarget endTarget,
|
||||
long duration) {
|
||||
long duration, RecentsAnimationCallbacks callbacks) {
|
||||
LauncherTaskbarUIController uiController = getTaskbarController();
|
||||
Animator superAnimator = super.getParallelAnimationToLauncher(endTarget, duration);
|
||||
if (uiController == null) {
|
||||
Animator superAnimator = super.getParallelAnimationToLauncher(
|
||||
endTarget, duration, callbacks);
|
||||
if (uiController == null || callbacks == null) {
|
||||
return superAnimator;
|
||||
}
|
||||
LauncherState toState = stateFromGestureEndTarget(endTarget);
|
||||
Animator taskbarAnimator = uiController.createAnimToLauncher(toState, duration);
|
||||
Animator taskbarAnimator = uiController.createAnimToLauncher(toState, callbacks, duration);
|
||||
if (superAnimator == null) {
|
||||
return taskbarAnimator;
|
||||
} else {
|
||||
|
||||
@@ -18,18 +18,14 @@ package com.android.quickstep;
|
||||
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
|
||||
import static com.android.launcher3.LauncherState.NORMAL;
|
||||
import static com.android.launcher3.Utilities.boundToRange;
|
||||
import static com.android.launcher3.Utilities.dpToPx;
|
||||
import static com.android.launcher3.Utilities.mapBoundToRange;
|
||||
import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE;
|
||||
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
|
||||
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
|
||||
import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
|
||||
|
||||
import static java.lang.Math.round;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
@@ -59,10 +55,8 @@ import com.android.launcher3.util.ObjectWrapper;
|
||||
import com.android.launcher3.views.FloatingIconView;
|
||||
import com.android.launcher3.views.FloatingView;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetHostView;
|
||||
import com.android.quickstep.util.AppCloseConfig;
|
||||
import com.android.quickstep.util.RectFSpringAnim;
|
||||
import com.android.quickstep.util.StaggeredWorkspaceAnim;
|
||||
import com.android.quickstep.util.WorkspaceRevealAnim;
|
||||
import com.android.quickstep.views.FloatingWidgetView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
@@ -106,9 +100,11 @@ public class LauncherSwipeHandlerV2 extends
|
||||
boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow();
|
||||
|
||||
mActivity.getRootView().setForceHideBackArrow(true);
|
||||
mActivity.setHintUserWillBeActive();
|
||||
if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
|
||||
mActivity.setHintUserWillBeActive();
|
||||
}
|
||||
|
||||
if (!canUseWorkspaceView || appCanEnterPip) {
|
||||
if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForStagedSplit) {
|
||||
return new LauncherHomeAnimationFactory();
|
||||
}
|
||||
if (workspaceView instanceof LauncherAppWidgetHostView) {
|
||||
@@ -119,8 +115,6 @@ public class LauncherSwipeHandlerV2 extends
|
||||
}
|
||||
|
||||
private HomeAnimationFactory createIconHomeAnimationFactory(View workspaceView) {
|
||||
final ResourceProvider rp = DynamicResource.provider(mActivity);
|
||||
final float transY = dpToPx(rp.getFloat(R.dimen.swipe_up_trans_y_dp));
|
||||
RectF iconLocation = new RectF();
|
||||
FloatingIconView floatingIconView = getFloatingIconView(mActivity, workspaceView,
|
||||
true /* hideOriginal */, iconLocation, false /* isOpening */);
|
||||
@@ -131,13 +125,15 @@ public class LauncherSwipeHandlerV2 extends
|
||||
|
||||
return new FloatingViewHomeAnimationFactory(floatingIconView) {
|
||||
|
||||
// There is a delay in loading the icon, so we need to keep the window
|
||||
// opaque until it is ready.
|
||||
private boolean mIsFloatingIconReady = false;
|
||||
@Nullable
|
||||
@Override
|
||||
protected View getViewIgnoredInWorkspaceRevealAnimation() {
|
||||
return workspaceView;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RectF getWindowTargetRect() {
|
||||
super.getWindowTargetRect();
|
||||
return iconLocation;
|
||||
}
|
||||
|
||||
@@ -150,24 +146,9 @@ public class LauncherSwipeHandlerV2 extends
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keepWindowOpaque() {
|
||||
if (mIsFloatingIconReady || floatingIconView.isVisibleToUser()) {
|
||||
mIsFloatingIconReady = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@Nullable AppCloseConfig config, RectF currentRect,
|
||||
float progress, float radius) {
|
||||
super.update(config, currentRect, progress, radius);
|
||||
int fgAlpha = 255;
|
||||
if (config != null && PROTOTYPE_APP_CLOSE.get()) {
|
||||
progress = config.getInterpolatedProgress();
|
||||
fgAlpha = config.getFgAlpha();
|
||||
}
|
||||
floatingIconView.update(1f, fgAlpha, currentRect, progress,
|
||||
public void update(RectF currentRect, float progress, float radius) {
|
||||
super.update(currentRect, progress, radius);
|
||||
floatingIconView.update(1f /* alpha */, 255 /* fgAlpha */, currentRect, progress,
|
||||
windowAlphaThreshold, radius, false);
|
||||
}
|
||||
};
|
||||
@@ -179,14 +160,16 @@ public class LauncherSwipeHandlerV2 extends
|
||||
final float floatingWidgetAlpha = isTargetTranslucent ? 0 : 1;
|
||||
RectF backgroundLocation = new RectF();
|
||||
Rect crop = new Rect();
|
||||
mTaskViewSimulator.getCurrentCropRect().roundOut(crop);
|
||||
// We can assume there is only one remote target here because staged split never animates
|
||||
// into the app icon, only into the homescreen
|
||||
mRemoteTargetHandles[0].getTaskViewSimulator().getCurrentCropRect().roundOut(crop);
|
||||
Size windowSize = new Size(crop.width(), crop.height());
|
||||
int fallbackBackgroundColor =
|
||||
FloatingWidgetView.getDefaultBackgroundColor(mContext, runningTaskTarget);
|
||||
FloatingWidgetView floatingWidgetView = FloatingWidgetView.getFloatingWidgetView(mActivity,
|
||||
hostView, backgroundLocation, windowSize,
|
||||
mTaskViewSimulator.getCurrentCornerRadius(), isTargetTranslucent,
|
||||
fallbackBackgroundColor);
|
||||
mRemoteTargetHandles[0].getTaskViewSimulator().getCurrentCornerRadius(),
|
||||
isTargetTranslucent, fallbackBackgroundColor);
|
||||
|
||||
return new FloatingViewHomeAnimationFactory(floatingWidgetView) {
|
||||
|
||||
@@ -217,14 +200,8 @@ public class LauncherSwipeHandlerV2 extends
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keepWindowOpaque() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@Nullable AppCloseConfig config, RectF currentRect, float progress,
|
||||
float radius) {
|
||||
super.update(config, currentRect, progress, radius);
|
||||
public void update(RectF currentRect, float progress, float radius) {
|
||||
super.update(currentRect, progress, radius);
|
||||
final float fallbackBackgroundAlpha =
|
||||
1 - mapBoundToRange(progress, 0.8f, 1, 0, 1, EXAGGERATED_EASE);
|
||||
final float foregroundAlpha =
|
||||
@@ -267,7 +244,7 @@ public class LauncherSwipeHandlerV2 extends
|
||||
}
|
||||
}
|
||||
|
||||
return mActivity.getWorkspace().getFirstMatchForAppClose(launchCookieItemId,
|
||||
return mActivity.getFirstMatchForAppClose(launchCookieItemId,
|
||||
runningTaskView.getTask().key.getComponent().getPackageName(),
|
||||
UserHandle.of(runningTaskView.getTask().key.userId));
|
||||
}
|
||||
@@ -283,27 +260,12 @@ public class LauncherSwipeHandlerV2 extends
|
||||
private final float mTransY;
|
||||
private final FloatingView mFloatingView;
|
||||
private ValueAnimator mBounceBackAnimator;
|
||||
private final AnimatorSet mWorkspaceReveal;
|
||||
|
||||
FloatingViewHomeAnimationFactory(FloatingView floatingView) {
|
||||
mFloatingView = floatingView;
|
||||
|
||||
ResourceProvider rp = DynamicResource.provider(mActivity);
|
||||
mTransY = dpToPx(rp.getFloat(R.dimen.swipe_up_trans_y_dp));
|
||||
|
||||
mWorkspaceReveal = PROTOTYPE_APP_CLOSE.get()
|
||||
? new WorkspaceRevealAnim(mActivity, true /* animateScrim */).getAnimators()
|
||||
: null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull RectF getWindowTargetRect() {
|
||||
if (PROTOTYPE_APP_CLOSE.get()) {
|
||||
// We want the target rect to be at this offset position, so that all
|
||||
// launcher content can spring back upwards.
|
||||
mFloatingView.setPositionOffsetY(mTransY);
|
||||
}
|
||||
return super.getWindowTargetRect();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -311,20 +273,6 @@ public class LauncherSwipeHandlerV2 extends
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@Nullable AppCloseConfig config, RectF currentRect, float progress,
|
||||
float radius) {
|
||||
if (config != null && PROTOTYPE_APP_CLOSE.get()) {
|
||||
DragLayer dl = mActivity.getDragLayer();
|
||||
float translationY = config.getWorkspaceTransY();
|
||||
dl.setTranslationY(translationY);
|
||||
|
||||
long duration = mWorkspaceReveal.getDuration();
|
||||
long playTime = boundToRange(round(duration * progress), 0, duration);
|
||||
mWorkspaceReveal.setCurrentPlayTime(playTime);
|
||||
}
|
||||
}
|
||||
|
||||
protected void bounceBackToRestingPosition() {
|
||||
final float startValue = mTransY;
|
||||
final float endValue = 0;
|
||||
@@ -358,31 +306,6 @@ public class LauncherSwipeHandlerV2 extends
|
||||
mBounceBackAnimator.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAnimation(RectFSpringAnim anim) {
|
||||
if (PROTOTYPE_APP_CLOSE.get()) {
|
||||
// Use a spring to put drag layer translation back to 0.
|
||||
anim.addAnimatorListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mFloatingView.setPositionOffsetY(0);
|
||||
bounceBackToRestingPosition();
|
||||
}
|
||||
});
|
||||
|
||||
// Will be updated manually below so that the two animations are in sync.
|
||||
mWorkspaceReveal.start();
|
||||
mWorkspaceReveal.pause();
|
||||
|
||||
anim.addAnimatorListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mWorkspaceReveal.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
mFloatingView.fastFinish();
|
||||
@@ -415,13 +338,9 @@ public class LauncherSwipeHandlerV2 extends
|
||||
|
||||
@Override
|
||||
public void playAtomicAnimation(float velocity) {
|
||||
if (!PROTOTYPE_APP_CLOSE.get()) {
|
||||
new StaggeredWorkspaceAnim(mActivity, velocity, true /* animateOverviewScrim */,
|
||||
getViewIgnoredInWorkspaceRevealAnimation())
|
||||
.start();
|
||||
} else if (shouldPlayAtomicWorkspaceReveal()) {
|
||||
new WorkspaceRevealAnim(mActivity, true).start();
|
||||
}
|
||||
new StaggeredWorkspaceAnim(mActivity, velocity, true /* animateOverviewScrim */,
|
||||
getViewIgnoredInWorkspaceRevealAnimation())
|
||||
.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.states.RotationHelper.deltaRotation;
|
||||
import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
|
||||
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.RectF;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
public class OrientationRectF extends RectF {
|
||||
|
||||
private static final String TAG = "OrientationRectF";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private final int mRotation;
|
||||
private final float mHeight;
|
||||
private final float mWidth;
|
||||
|
||||
private final Matrix mTmpMatrix = new Matrix();
|
||||
private final float[] mTmpPoint = new float[2];
|
||||
|
||||
public OrientationRectF(float left, float top, float right, float bottom, int rotation) {
|
||||
super(left, top, right, bottom);
|
||||
mRotation = rotation;
|
||||
mHeight = bottom;
|
||||
mWidth = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String s = super.toString();
|
||||
s += " rotation: " + mRotation;
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(float x, float y) {
|
||||
// Mark bottom right as included in the Rect (copied from Rect src, added "=" in "<=")
|
||||
return left < right && top < bottom // check for empty first
|
||||
&& x >= left && x <= right && y >= top && y <= bottom;
|
||||
}
|
||||
|
||||
public boolean applyTransformFromRotation(MotionEvent event, int currentRotation,
|
||||
boolean forceTransform) {
|
||||
return applyTransform(event, deltaRotation(currentRotation, mRotation), forceTransform);
|
||||
}
|
||||
|
||||
public boolean applyTransformToRotation(MotionEvent event, int currentRotation,
|
||||
boolean forceTransform) {
|
||||
return applyTransform(event, deltaRotation(mRotation, currentRotation), forceTransform);
|
||||
}
|
||||
|
||||
private boolean applyTransform(MotionEvent event, int deltaRotation, boolean forceTransform) {
|
||||
mTmpMatrix.reset();
|
||||
postDisplayRotation(deltaRotation, mHeight, mWidth, mTmpMatrix);
|
||||
if (forceTransform) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Transforming rotation due to forceTransform, "
|
||||
+ "deltaRotation: " + deltaRotation
|
||||
+ "mRotation: " + mRotation
|
||||
+ " this: " + this);
|
||||
}
|
||||
event.applyTransform(mTmpMatrix);
|
||||
return true;
|
||||
}
|
||||
mTmpPoint[0] = event.getX();
|
||||
mTmpPoint[1] = event.getY();
|
||||
mTmpMatrix.mapPoints(mTmpPoint);
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "original: " + event.getX() + ", " + event.getY()
|
||||
+ " new: " + mTmpPoint[0] + ", " + mTmpPoint[1]
|
||||
+ " rect: " + this + " forceTransform: " + forceTransform
|
||||
+ " contains: " + contains(mTmpPoint[0], mTmpPoint[1])
|
||||
+ " this: " + this);
|
||||
}
|
||||
|
||||
if (contains(mTmpPoint[0], mTmpPoint[1])) {
|
||||
event.applyTransform(mTmpMatrix);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int getRotation() {
|
||||
return mRotation;
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,7 @@ import static android.view.MotionEvent.ACTION_MOVE;
|
||||
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
|
||||
import static android.view.MotionEvent.ACTION_UP;
|
||||
|
||||
import static com.android.launcher3.states.RotationHelper.deltaRotation;
|
||||
import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.RectF;
|
||||
import android.util.Log;
|
||||
@@ -44,8 +40,8 @@ import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Maintains state for supporting nav bars and tracking their gestures in multiple orientations.
|
||||
* See {@link OrientationRectF#applyTransform(MotionEvent, boolean)} for transformation of
|
||||
* MotionEvents from one orientation's coordinate space to another's.
|
||||
* See {@link OrientationRectF#applyTransformToRotation(MotionEvent, int, boolean)} for
|
||||
* transformation of MotionEvents from one orientation's coordinate space to another's.
|
||||
*
|
||||
* This class only supports single touch/pointer gesture tracking for touches started in a supported
|
||||
* nav bar region.
|
||||
@@ -95,9 +91,6 @@ class OrientationTouchTransformer {
|
||||
|
||||
private static final int QUICKSTEP_ROTATION_UNINITIALIZED = -1;
|
||||
|
||||
private final Matrix mTmpMatrix = new Matrix();
|
||||
private final float[] mTmpPoint = new float[2];
|
||||
|
||||
private final Map<CurrentDisplay, OrientationRectF> mSwipeTouchRegions =
|
||||
new HashMap<CurrentDisplay, OrientationRectF>();
|
||||
private final RectF mAssistantLeftRegion = new RectF();
|
||||
@@ -365,7 +358,7 @@ class OrientationTouchTransformer {
|
||||
if (mLastRectTouched == null) {
|
||||
return;
|
||||
}
|
||||
mLastRectTouched.applyTransform(event, true);
|
||||
mLastRectTouched.applyTransformFromRotation(event, mCurrentDisplay.rotation, true);
|
||||
break;
|
||||
}
|
||||
case ACTION_CANCEL:
|
||||
@@ -373,7 +366,7 @@ class OrientationTouchTransformer {
|
||||
if (mLastRectTouched == null) {
|
||||
return;
|
||||
}
|
||||
mLastRectTouched.applyTransform(event, true);
|
||||
mLastRectTouched.applyTransformFromRotation(event, mCurrentDisplay.rotation, true);
|
||||
mLastRectTouched = null;
|
||||
break;
|
||||
}
|
||||
@@ -387,14 +380,14 @@ class OrientationTouchTransformer {
|
||||
if (rect == null) {
|
||||
continue;
|
||||
}
|
||||
if (rect.applyTransform(event, false)) {
|
||||
if (rect.applyTransformFromRotation(event, mCurrentDisplay.rotation, false)) {
|
||||
mLastRectTouched = rect;
|
||||
mActiveTouchRotation = rect.mRotation;
|
||||
mActiveTouchRotation = rect.getRotation();
|
||||
if (mEnableMultipleRegions
|
||||
&& mCurrentDisplay.rotation == mActiveTouchRotation) {
|
||||
// TODO(b/154580671) might make this block unnecessary
|
||||
// Start a touch session for the default nav region for the display
|
||||
mQuickStepStartingRotation = mLastRectTouched.mRotation;
|
||||
mQuickStepStartingRotation = mLastRectTouched.getRotation();
|
||||
resetSwipeRegions();
|
||||
}
|
||||
if (DEBUG) {
|
||||
@@ -423,65 +416,4 @@ class OrientationTouchTransformer {
|
||||
pw.println(" mNavBarLargerGesturalHeight=" + mNavBarLargerGesturalHeight);
|
||||
pw.println(" mOneHandedModeRegion=" + mOneHandedModeRegion);
|
||||
}
|
||||
|
||||
private class OrientationRectF extends RectF {
|
||||
|
||||
private int mRotation;
|
||||
private float mHeight;
|
||||
private float mWidth;
|
||||
|
||||
OrientationRectF(float left, float top, float right, float bottom, int rotation) {
|
||||
super(left, top, right, bottom);
|
||||
this.mRotation = rotation;
|
||||
mHeight = bottom;
|
||||
mWidth = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String s = super.toString();
|
||||
s += " rotation: " + mRotation;
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(float x, float y) {
|
||||
// Mark bottom right as included in the Rect (copied from Rect src, added "=" in "<=")
|
||||
return left < right && top < bottom // check for empty first
|
||||
&& x >= left && x <= right && y >= top && y <= bottom;
|
||||
}
|
||||
|
||||
boolean applyTransform(MotionEvent event, boolean forceTransform) {
|
||||
mTmpMatrix.reset();
|
||||
postDisplayRotation(deltaRotation(mCurrentDisplay.rotation, mRotation),
|
||||
mHeight, mWidth, mTmpMatrix);
|
||||
if (forceTransform) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Transforming rotation due to forceTransform, "
|
||||
+ "mCurrentRotation: " + mCurrentDisplay.rotation
|
||||
+ "mRotation: " + mRotation
|
||||
+ " this: " + this);
|
||||
}
|
||||
event.transform(mTmpMatrix);
|
||||
return true;
|
||||
}
|
||||
mTmpPoint[0] = event.getX();
|
||||
mTmpPoint[1] = event.getY();
|
||||
mTmpMatrix.mapPoints(mTmpPoint);
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "original: " + event.getX() + ", " + event.getY()
|
||||
+ " new: " + mTmpPoint[0] + ", " + mTmpPoint[1]
|
||||
+ " rect: " + this + " forceTransform: " + forceTransform
|
||||
+ " contains: " + contains(mTmpPoint[0], mTmpPoint[1])
|
||||
+ " this: " + this);
|
||||
}
|
||||
|
||||
if (contains(mTmpPoint[0], mTmpPoint[1])) {
|
||||
event.transform(mTmpMatrix);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.MainThreadInitializedObject.forOverride;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.util.ResourceBasedOverride;
|
||||
import com.android.systemui.plugins.OverscrollPlugin;
|
||||
|
||||
/**
|
||||
* Resource overrideable factory for forcing a local overscroll plugin.
|
||||
* Override {@link R.string#overscroll_plugin_factory_class} to set a different class.
|
||||
*/
|
||||
public class OverscrollPluginFactory implements ResourceBasedOverride {
|
||||
public static final MainThreadInitializedObject<OverscrollPluginFactory> INSTANCE = forOverride(
|
||||
OverscrollPluginFactory.class,
|
||||
R.string.overscroll_plugin_factory_class);
|
||||
|
||||
/**
|
||||
* Get the plugin that is defined locally in launcher, as opposed to a dynamic side loaded one.
|
||||
*/
|
||||
public OverscrollPlugin getLocalOverscrollPlugin() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -32,12 +32,15 @@ import androidx.annotation.UiThread;
|
||||
import com.android.launcher3.statemanager.StatefulActivity;
|
||||
import com.android.launcher3.util.RunnableList;
|
||||
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
|
||||
import com.android.quickstep.util.LauncherSplitScreenListener;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Helper class to handle various atomic commands for switching between Overview.
|
||||
@@ -49,6 +52,7 @@ public class OverviewCommandHelper {
|
||||
public static final int TYPE_SHOW_NEXT_FOCUS = 2;
|
||||
public static final int TYPE_HIDE = 3;
|
||||
public static final int TYPE_TOGGLE = 4;
|
||||
public static final int TYPE_HOME = 5;
|
||||
|
||||
private static final String TRANSITION_NAME = "Transition:toOverview";
|
||||
|
||||
@@ -114,11 +118,12 @@ public class OverviewCommandHelper {
|
||||
mPendingCommands.clear();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TaskView getNextTask(RecentsView view) {
|
||||
final TaskView runningTaskView = view.getRunningTaskView();
|
||||
|
||||
if (runningTaskView == null) {
|
||||
return view.getTaskViewCount() > 0 ? view.getTaskViewAt(0) : null;
|
||||
return view.getTaskViewAt(0);
|
||||
} else {
|
||||
final TaskView nextTask = view.getNextTaskView();
|
||||
return nextTask != null ? nextTask : runningTaskView;
|
||||
@@ -154,6 +159,11 @@ public class OverviewCommandHelper {
|
||||
// already hidden
|
||||
return true;
|
||||
}
|
||||
if (cmd.type == TYPE_HOME) {
|
||||
mService.startActivity(mOverviewComponentObserver.getHomeIntent());
|
||||
LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
switch (cmd.type) {
|
||||
case TYPE_SHOW:
|
||||
@@ -168,6 +178,10 @@ public class OverviewCommandHelper {
|
||||
}
|
||||
case TYPE_TOGGLE:
|
||||
return launchTask(recents, getNextTask(recents), cmd);
|
||||
case TYPE_HOME:
|
||||
recents.startHome();
|
||||
LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +214,7 @@ public class OverviewCommandHelper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
|
||||
public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
|
||||
interactionHandler.onGestureCancelled();
|
||||
cmd.removeListener(this);
|
||||
|
||||
@@ -244,8 +258,8 @@ public class OverviewCommandHelper {
|
||||
// Ensure that recents view has focus so that it receives the followup key inputs
|
||||
TaskView taskView = rv.getNextTaskView();
|
||||
if (taskView == null) {
|
||||
if (rv.getTaskViewCount() > 0) {
|
||||
taskView = rv.getTaskViewAt(0);
|
||||
taskView = rv.getTaskViewAt(0);
|
||||
if (taskView != null) {
|
||||
taskView.requestFocus();
|
||||
} else {
|
||||
rv.requestFocus();
|
||||
@@ -258,6 +272,14 @@ public class OverviewCommandHelper {
|
||||
scheduleNextTask(cmd);
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw) {
|
||||
pw.println("OverviewCommandHelper:");
|
||||
pw.println(" mPendingCommands=" + mPendingCommands.size());
|
||||
if (!mPendingCommands.isEmpty()) {
|
||||
pw.println(" pendingCommandType=" + mPendingCommands.get(0).type);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CommandInfo {
|
||||
public final long createTime = SystemClock.elapsedRealtime();
|
||||
public final int type;
|
||||
|
||||
@@ -24,8 +24,6 @@ import android.util.Log;
|
||||
|
||||
import com.android.launcher3.BuildConfig;
|
||||
import com.android.launcher3.MainProcessInitializer;
|
||||
import com.android.launcher3.util.Executors;
|
||||
import com.android.quickstep.logging.SettingsChangeLogger;
|
||||
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
|
||||
import com.android.systemui.shared.system.ThreadedRendererCompat;
|
||||
|
||||
@@ -62,9 +60,5 @@ public class QuickstepProcessInitializer extends MainProcessInitializer {
|
||||
// Elevate GPU priority for Quickstep and Remote animations.
|
||||
ThreadedRendererCompat.setContextPriority(
|
||||
ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
|
||||
|
||||
// Initialize settings logger after a default timeout
|
||||
Executors.MAIN_EXECUTOR.getHandler()
|
||||
.postDelayed(() -> new SettingsChangeLogger(context), SETUP_DELAY_MILLIS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ package com.android.quickstep;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.testing.TestInformationHandler;
|
||||
import com.android.launcher3.testing.TestProtocol;
|
||||
import com.android.launcher3.touch.PagedOrientationHandler;
|
||||
@@ -21,7 +21,7 @@ public class QuickstepTestInformationHandler extends TestInformationHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle call(String method) {
|
||||
public Bundle call(String method, String arg) {
|
||||
final Bundle response = new Bundle();
|
||||
switch (method) {
|
||||
case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
|
||||
@@ -53,20 +53,30 @@ public class QuickstepTestInformationHandler extends TestInformationHandler {
|
||||
Bundle::putInt, PortraitStatesTouchController::getHotseatTop);
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_OVERVIEW_SHARE_ENABLED: {
|
||||
response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
|
||||
FeatureFlags.ENABLE_OVERVIEW_SHARE.get());
|
||||
case TestProtocol.REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET: {
|
||||
if (!mDeviceProfile.isTablet) {
|
||||
return null;
|
||||
}
|
||||
Rect focusedTaskRect = new Rect();
|
||||
LauncherActivityInterface.INSTANCE.calculateTaskSize(mContext, mDeviceProfile,
|
||||
focusedTaskRect);
|
||||
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, focusedTaskRect.height());
|
||||
return response;
|
||||
}
|
||||
|
||||
case TestProtocol.REQUEST_OVERVIEW_CONTENT_PUSH_ENABLED: {
|
||||
response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
|
||||
FeatureFlags.ENABLE_OVERVIEW_CONTENT_PUSH.get());
|
||||
case TestProtocol.REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET: {
|
||||
if (!mDeviceProfile.isTablet) {
|
||||
return null;
|
||||
}
|
||||
Rect gridTaskRect = new Rect();
|
||||
LauncherActivityInterface.INSTANCE.calculateGridTaskSize(mContext, mDeviceProfile,
|
||||
gridTaskRect, PagedOrientationHandler.PORTRAIT);
|
||||
response.putParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD, gridTaskRect);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
return super.call(method);
|
||||
return super.call(method, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,22 +22,23 @@ import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
import android.os.RemoteException;
|
||||
import android.util.SparseBooleanArray;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.launcher3.testing.TestProtocol;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.launcher3.util.LooperExecutor;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.KeyguardManagerCompat;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListeners;
|
||||
import com.android.wm.shell.recents.IRecentTasksListener;
|
||||
import com.android.wm.shell.util.GroupedRecentTaskInfo;
|
||||
import com.android.wm.shell.util.StagedSplitBounds;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import app.lawnchair.LawnchairApp;
|
||||
@@ -46,13 +47,13 @@ import app.lawnchair.LawnchairApp;
|
||||
* Manages the recent task list from the system, caching it as necessary.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.R)
|
||||
public class RecentTasksList extends TaskStackChangeListener {
|
||||
public class RecentTasksList {
|
||||
|
||||
private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0);
|
||||
|
||||
private final KeyguardManagerCompat mKeyguardManager;
|
||||
private final LooperExecutor mMainThreadExecutor;
|
||||
private final ActivityManagerWrapper mActivityManagerWrapper;
|
||||
private final SystemUiProxy mSysUiProxy;
|
||||
|
||||
// The list change id, increments as the task list changes in the system
|
||||
private int mChangeId;
|
||||
@@ -64,14 +65,17 @@ public class RecentTasksList extends TaskStackChangeListener {
|
||||
private TaskLoadResult mResultsUi = INVALID_RESULT;
|
||||
|
||||
public RecentTasksList(LooperExecutor mainThreadExecutor,
|
||||
KeyguardManagerCompat keyguardManager, ActivityManagerWrapper activityManagerWrapper) {
|
||||
KeyguardManagerCompat keyguardManager, SystemUiProxy sysUiProxy) {
|
||||
mMainThreadExecutor = mainThreadExecutor;
|
||||
mKeyguardManager = keyguardManager;
|
||||
mChangeId = 1;
|
||||
mActivityManagerWrapper = activityManagerWrapper;
|
||||
if (LawnchairApp.isRecentsEnabled()) {
|
||||
TaskStackChangeListeners.getInstance().registerTaskStackListener(this);
|
||||
}
|
||||
mSysUiProxy = sysUiProxy;
|
||||
sysUiProxy.registerRecentTasksListener(new IRecentTasksListener.Stub() {
|
||||
@Override
|
||||
public void onRecentTasksChanged() throws RemoteException {
|
||||
mMainThreadExecutor.execute(RecentTasksList.this::onRecentTasksChanged);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -82,10 +86,11 @@ public class RecentTasksList extends TaskStackChangeListener {
|
||||
/**
|
||||
* Fetches the task keys skipping any local cache.
|
||||
*/
|
||||
public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) {
|
||||
public void getTaskKeys(int numTasks, Consumer<ArrayList<GroupTask>> callback) {
|
||||
// Kick off task loading in the background
|
||||
UI_HELPER_EXECUTOR.execute(() -> {
|
||||
ArrayList<Task> tasks = loadTasksInBackground(numTasks, -1, true /* loadKeysOnly */);
|
||||
ArrayList<GroupTask> tasks = loadTasksInBackground(numTasks, -1,
|
||||
true /* loadKeysOnly */);
|
||||
mMainThreadExecutor.execute(() -> callback.accept(tasks));
|
||||
});
|
||||
}
|
||||
@@ -97,14 +102,15 @@ public class RecentTasksList extends TaskStackChangeListener {
|
||||
* @param callback The callback to receive the list of recent tasks
|
||||
* @return The change id of the current task list
|
||||
*/
|
||||
public synchronized int getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback) {
|
||||
public synchronized int getTasks(boolean loadKeysOnly,
|
||||
Consumer<ArrayList<GroupTask>> callback) {
|
||||
final int requestLoadId = mChangeId;
|
||||
if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) {
|
||||
// The list is up to date, send the callback on the next frame,
|
||||
// so that requestID can be returned first.
|
||||
if (callback != null) {
|
||||
// Copy synchronously as the changeId might change by next frame
|
||||
ArrayList<Task> result = copyOf(mResultsUi);
|
||||
ArrayList<GroupTask> result = copyOf(mResultsUi);
|
||||
mMainThreadExecutor.post(() -> {
|
||||
callback.accept(result);
|
||||
});
|
||||
@@ -124,7 +130,7 @@ public class RecentTasksList extends TaskStackChangeListener {
|
||||
mLoadingTasksInBackground = false;
|
||||
mResultsUi = loadResult;
|
||||
if (callback != null) {
|
||||
ArrayList<Task> result = copyOf(mResultsUi);
|
||||
ArrayList<GroupTask> result = copyOf(mResultsUi);
|
||||
callback.accept(result);
|
||||
}
|
||||
});
|
||||
@@ -140,35 +146,7 @@ public class RecentTasksList extends TaskStackChangeListener {
|
||||
return mChangeId == changeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskStackChanged() {
|
||||
invalidateLoadedTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecentTaskListUpdated() {
|
||||
// In some cases immediately after booting, the tasks in the system recent task list may be
|
||||
// loaded, but not in the active task hierarchy in the system. These tasks are displayed in
|
||||
// overview, but removing them don't result in a onTaskStackChanged() nor a onTaskRemoved()
|
||||
// callback (those are for changes to the active tasks), but the task list is still updated,
|
||||
// so we should also invalidate the change id to ensure we load a new list instead of
|
||||
// reusing a stale list.
|
||||
invalidateLoadedTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskRemoved(int taskId) {
|
||||
invalidateLoadedTasks();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
|
||||
invalidateLoadedTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onActivityUnpinned() {
|
||||
public void onRecentTasksChanged() {
|
||||
invalidateLoadedTasks();
|
||||
}
|
||||
|
||||
@@ -184,8 +162,8 @@ public class RecentTasksList extends TaskStackChangeListener {
|
||||
@VisibleForTesting
|
||||
TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) {
|
||||
int currentUserId = Process.myUserHandle().getIdentifier();
|
||||
List<ActivityManager.RecentTaskInfo> rawTasks =
|
||||
mActivityManagerWrapper.getRecentTasks(numTasks, currentUserId);
|
||||
ArrayList<GroupedRecentTaskInfo> rawTasks =
|
||||
mSysUiProxy.getRecentTasks(numTasks, currentUserId);
|
||||
// The raw tasks are given in most-recent to least-recent order, we need to reverse it
|
||||
Collections.reverse(rawTasks);
|
||||
|
||||
@@ -201,45 +179,84 @@ public class RecentTasksList extends TaskStackChangeListener {
|
||||
};
|
||||
|
||||
TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
|
||||
for (ActivityManager.RecentTaskInfo rawTask : rawTasks) {
|
||||
Task.TaskKey taskKey = new Task.TaskKey(rawTask);
|
||||
Task task;
|
||||
if (!loadKeysOnly) {
|
||||
boolean isLocked = tmpLockedUsers.get(taskKey.userId);
|
||||
task = Task.from(taskKey, rawTask, isLocked);
|
||||
} else {
|
||||
task = new Task(taskKey);
|
||||
for (GroupedRecentTaskInfo rawTask : rawTasks) {
|
||||
ActivityManager.RecentTaskInfo taskInfo1 = rawTask.mTaskInfo1;
|
||||
ActivityManager.RecentTaskInfo taskInfo2 = rawTask.mTaskInfo2;
|
||||
Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
|
||||
Task task1 = loadKeysOnly
|
||||
? new Task(task1Key)
|
||||
: Task.from(task1Key, taskInfo1,
|
||||
tmpLockedUsers.get(task1Key.userId) /* isLocked */);
|
||||
task1.setLastSnapshotData(taskInfo1);
|
||||
Task task2 = null;
|
||||
if (taskInfo2 != null) {
|
||||
Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
|
||||
task2 = loadKeysOnly
|
||||
? new Task(task2Key)
|
||||
: Task.from(task2Key, taskInfo2,
|
||||
tmpLockedUsers.get(task2Key.userId) /* isLocked */);
|
||||
task2.setLastSnapshotData(taskInfo2);
|
||||
}
|
||||
task.setLastSnapshotData(rawTask);
|
||||
allTasks.add(task);
|
||||
final SplitConfigurationOptions.StagedSplitBounds launcherSplitBounds =
|
||||
convertSplitBounds(rawTask.mStagedSplitBounds);
|
||||
allTasks.add(new GroupTask(task1, task2, launcherSplitBounds));
|
||||
}
|
||||
|
||||
return allTasks;
|
||||
}
|
||||
|
||||
private ArrayList<Task> copyOf(ArrayList<Task> tasks) {
|
||||
ArrayList<Task> newTasks = new ArrayList<>();
|
||||
private SplitConfigurationOptions.StagedSplitBounds convertSplitBounds(
|
||||
StagedSplitBounds shellSplitBounds) {
|
||||
return shellSplitBounds == null ?
|
||||
null :
|
||||
new SplitConfigurationOptions.StagedSplitBounds(
|
||||
shellSplitBounds.leftTopBounds, shellSplitBounds.rightBottomBounds,
|
||||
shellSplitBounds.leftTopTaskId, shellSplitBounds.rightBottomTaskId);
|
||||
}
|
||||
|
||||
private ArrayList<GroupTask> copyOf(ArrayList<GroupTask> tasks) {
|
||||
ArrayList<GroupTask> newTasks = new ArrayList<>();
|
||||
for (int i = 0; i < tasks.size(); i++) {
|
||||
newTasks.add(new Task(tasks.get(i)));
|
||||
newTasks.add(new GroupTask(tasks.get(i)));
|
||||
}
|
||||
return newTasks;
|
||||
}
|
||||
|
||||
private static class TaskLoadResult extends ArrayList<Task> {
|
||||
public void dump(String prefix, PrintWriter writer) {
|
||||
writer.println(prefix + "RecentTasksList:");
|
||||
writer.println(prefix + " mChangeId=" + mChangeId);
|
||||
writer.println(prefix + " mResultsUi=[id=" + mResultsUi.mRequestId + ", tasks=");
|
||||
for (GroupTask task : mResultsUi) {
|
||||
writer.println(prefix + " t1=" + task.task1.key.id
|
||||
+ " t2=" + (task.hasMultipleTasks() ? task.task2.key.id : "-1"));
|
||||
}
|
||||
writer.println(prefix + " ]");
|
||||
int currentUserId = Process.myUserHandle().getIdentifier();
|
||||
ArrayList<GroupedRecentTaskInfo> rawTasks =
|
||||
mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId);
|
||||
writer.println(prefix + " rawTasks=[");
|
||||
for (GroupedRecentTaskInfo task : rawTasks) {
|
||||
writer.println(prefix + " t1=" + task.mTaskInfo1.taskId
|
||||
+ " t2=" + (task.mTaskInfo2 != null ? task.mTaskInfo2.taskId : "-1"));
|
||||
}
|
||||
writer.println(prefix + " ]");
|
||||
}
|
||||
|
||||
final int mId;
|
||||
private static class TaskLoadResult extends ArrayList<GroupTask> {
|
||||
|
||||
final int mRequestId;
|
||||
|
||||
// If the result was loaded with keysOnly = true
|
||||
final boolean mKeysOnly;
|
||||
|
||||
TaskLoadResult(int id, boolean keysOnly, int size) {
|
||||
TaskLoadResult(int requestId, boolean keysOnly, int size) {
|
||||
super(size);
|
||||
mId = id;
|
||||
mRequestId = requestId;
|
||||
mKeysOnly = keysOnly;
|
||||
}
|
||||
|
||||
boolean isValidForRequest(int requestId, boolean loadKeysOnly) {
|
||||
return mId == requestId && (!mKeysOnly || loadKeysOnly);
|
||||
return mRequestId == requestId && (!mKeysOnly || loadKeysOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,8 @@ import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DU
|
||||
import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION;
|
||||
import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
|
||||
import static com.android.launcher3.Utilities.createHomeIntent;
|
||||
import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
|
||||
import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
|
||||
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
|
||||
import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
|
||||
import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
|
||||
@@ -61,6 +61,8 @@ import com.android.launcher3.statemanager.StateManager;
|
||||
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
|
||||
import com.android.launcher3.statemanager.StateManager.StateHandler;
|
||||
import com.android.launcher3.statemanager.StatefulActivity;
|
||||
import com.android.launcher3.taskbar.FallbackTaskbarUIController;
|
||||
import com.android.launcher3.taskbar.TaskbarManager;
|
||||
import com.android.launcher3.util.ActivityOptionsWrapper;
|
||||
import com.android.launcher3.util.ActivityTracker;
|
||||
import com.android.launcher3.util.RunnableList;
|
||||
@@ -74,9 +76,9 @@ import com.android.quickstep.fallback.RecentsDragLayer;
|
||||
import com.android.quickstep.fallback.RecentsState;
|
||||
import com.android.quickstep.util.RecentsAtomicAnimationFactory;
|
||||
import com.android.quickstep.util.SplitSelectStateController;
|
||||
import com.android.quickstep.util.TISBindHelper;
|
||||
import com.android.quickstep.views.OverviewActionsView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.SplitPlaceholderView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.systemui.shared.system.ActivityOptionsCompat;
|
||||
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
|
||||
@@ -98,11 +100,15 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> {
|
||||
private Handler mUiHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
private static final long HOME_APPEAR_DURATION = 250;
|
||||
private static final long RECENTS_ANIMATION_TIMEOUT = 1000;
|
||||
|
||||
private RecentsDragLayer mDragLayer;
|
||||
private ScrimView mScrimView;
|
||||
private FallbackRecentsView mFallbackRecentsView;
|
||||
private OverviewActionsView mActionsView;
|
||||
private TISBindHelper mTISBindHelper;
|
||||
private @Nullable TaskbarManager mTaskbarManager;
|
||||
private @Nullable FallbackTaskbarUIController mTaskbarUIController;
|
||||
|
||||
private Configuration mOldConfig;
|
||||
|
||||
@@ -111,6 +117,11 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> {
|
||||
// Strong refs to runners which are cleared when the activity is destroyed
|
||||
private RemoteAnimationFactory mActivityLaunchAnimationRunner;
|
||||
|
||||
// For handling degenerate cases where starting an activity doesn't actually trigger the remote
|
||||
// animation callback
|
||||
private final Handler mHandler = new Handler();
|
||||
private final Runnable mAnimationStartTimeoutRunnable = this::onAnimationStartTimeout;
|
||||
|
||||
/**
|
||||
* Init drag layer and overview panel views.
|
||||
*/
|
||||
@@ -123,13 +134,31 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> {
|
||||
mActionsView = findViewById(R.id.overview_actions_view);
|
||||
SYSUI_PROGRESS.set(getRootView().getSysUiScrim(), 0f);
|
||||
|
||||
SplitPlaceholderView splitPlaceholderView = findViewById(R.id.split_placeholder);
|
||||
splitPlaceholderView.init(
|
||||
new SplitSelectStateController(mUiHandler, SystemUiProxy.INSTANCE.get(this))
|
||||
);
|
||||
|
||||
SplitSelectStateController controller =
|
||||
new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this),
|
||||
getStateManager(), null /*depthController*/);
|
||||
mDragLayer.recreateControllers();
|
||||
mFallbackRecentsView.init(mActionsView, splitPlaceholderView);
|
||||
mFallbackRecentsView.init(mActionsView, controller);
|
||||
|
||||
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
|
||||
}
|
||||
|
||||
private void onTISConnected(TouchInteractionService.TISBinder binder) {
|
||||
mTaskbarManager = binder.getTaskbarManager();
|
||||
mTaskbarManager.setActivity(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runOnBindToTouchInteractionService(Runnable r) {
|
||||
mTISBindHelper.runOnBindToTouchInteractionService(r);
|
||||
}
|
||||
|
||||
public void setTaskbarUIController(FallbackTaskbarUIController taskbarUIController) {
|
||||
mTaskbarUIController = taskbarUIController;
|
||||
}
|
||||
|
||||
public FallbackTaskbarUIController getTaskbarUIController() {
|
||||
return mTaskbarUIController;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -197,6 +226,16 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> {
|
||||
// TODO(b/137318995) This should go home, but doing so removes freeform windows
|
||||
}
|
||||
|
||||
/**
|
||||
* Called if the remote animation callback from #getActivityLaunchOptions() hasn't called back
|
||||
* in a reasonable time due to a conflict with the recents animation.
|
||||
*/
|
||||
private void onAnimationStartTimeout() {
|
||||
if (mActivityLaunchAnimationRunner != null) {
|
||||
mActivityLaunchAnimationRunner.onAnimationCancelled();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActivityOptionsWrapper getActivityLaunchOptions(final View v, @Nullable ItemInfo item) {
|
||||
if (!(v instanceof TaskView)) {
|
||||
@@ -211,6 +250,7 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> {
|
||||
public void onCreateAnimation(int transit, RemoteAnimationTargetCompat[] appTargets,
|
||||
RemoteAnimationTargetCompat[] wallpaperTargets,
|
||||
RemoteAnimationTargetCompat[] nonAppTargets, AnimationResult result) {
|
||||
mHandler.removeCallbacks(mAnimationStartTimeoutRunnable);
|
||||
AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets,
|
||||
wallpaperTargets, nonAppTargets);
|
||||
anim.addListener(resetStateListener());
|
||||
@@ -220,6 +260,7 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> {
|
||||
|
||||
@Override
|
||||
public void onAnimationCancelled() {
|
||||
mHandler.removeCallbacks(mAnimationStartTimeoutRunnable);
|
||||
onEndCallback.executeAllAndDestroy();
|
||||
}
|
||||
};
|
||||
@@ -229,13 +270,14 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> {
|
||||
RemoteAnimationAdapterCompat adapterCompat = new RemoteAnimationAdapterCompat(
|
||||
wrapper, RECENTS_LAUNCH_DURATION,
|
||||
RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION
|
||||
- STATUS_BAR_TRANSITION_PRE_DELAY);
|
||||
- STATUS_BAR_TRANSITION_PRE_DELAY, getIApplicationThread());
|
||||
final ActivityOptionsWrapper activityOptions = new ActivityOptionsWrapper(
|
||||
ActivityOptionsCompat.makeRemoteAnimation(adapterCompat),
|
||||
onEndCallback);
|
||||
if (Utilities.ATLEAST_S) {
|
||||
activityOptions.options.setSplashscreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
|
||||
}
|
||||
mHandler.postDelayed(mAnimationStartTimeoutRunnable, RECENTS_ANIMATION_TIMEOUT);
|
||||
return activityOptions;
|
||||
}
|
||||
|
||||
@@ -293,7 +335,7 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mStateManager = new StateManager<>(this, RecentsState.DEFAULT);
|
||||
mStateManager = new StateManager<>(this, RecentsState.BG_LAUNCHER);
|
||||
|
||||
mOldConfig = new Configuration(getResources().getConfiguration());
|
||||
initDeviceProfile();
|
||||
@@ -353,6 +395,11 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> {
|
||||
super.onDestroy();
|
||||
ACTIVITY_TRACKER.onActivityDestroyed(this);
|
||||
mActivityLaunchAnimationRunner = null;
|
||||
|
||||
mTISBindHelper.onDestroy();
|
||||
if (mTaskbarManager != null) {
|
||||
mTaskbarManager.clearActivity(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -375,7 +422,8 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> {
|
||||
LauncherAnimationRunner runner = new LauncherAnimationRunner(
|
||||
getMainThreadHandler(), mAnimationToHomeFactory, true);
|
||||
RemoteAnimationAdapterCompat adapterCompat =
|
||||
new RemoteAnimationAdapterCompat(runner, HOME_APPEAR_DURATION, 0);
|
||||
new RemoteAnimationAdapterCompat(runner, HOME_APPEAR_DURATION, 0,
|
||||
getIApplicationThread());
|
||||
startActivity(createHomeIntent(),
|
||||
ActivityOptionsCompat.makeRemoteAnimation(adapterCompat).toBundle());
|
||||
}
|
||||
|
||||
@@ -16,11 +16,14 @@
|
||||
package com.android.quickstep;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.util.ArraySet;
|
||||
import android.view.RemoteAnimationTarget;
|
||||
|
||||
import androidx.annotation.BinderThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.android.launcher3.Utilities;
|
||||
@@ -29,6 +32,8 @@ import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -39,6 +44,7 @@ public class RecentsAnimationCallbacks implements
|
||||
com.android.systemui.shared.system.RecentsAnimationListener {
|
||||
|
||||
private final Set<RecentsAnimationListener> mListeners = new ArraySet<>();
|
||||
private final SystemUiProxy mSystemUiProxy;
|
||||
private final boolean mAllowMinimizeSplitScreen;
|
||||
|
||||
// TODO(141886704): Remove these references when they are no longer needed
|
||||
@@ -46,7 +52,9 @@ public class RecentsAnimationCallbacks implements
|
||||
|
||||
private boolean mCancelled;
|
||||
|
||||
public RecentsAnimationCallbacks(boolean allowMinimizeSplitScreen) {
|
||||
public RecentsAnimationCallbacks(SystemUiProxy systemUiProxy,
|
||||
boolean allowMinimizeSplitScreen) {
|
||||
mSystemUiProxy = systemUiProxy;
|
||||
mAllowMinimizeSplitScreen = allowMinimizeSplitScreen;
|
||||
}
|
||||
|
||||
@@ -70,7 +78,7 @@ public class RecentsAnimationCallbacks implements
|
||||
|
||||
public void notifyAnimationCanceled() {
|
||||
mCancelled = true;
|
||||
onAnimationCanceled(null);
|
||||
onAnimationCanceled(new HashMap<>());
|
||||
}
|
||||
|
||||
// Called only in Q platform
|
||||
@@ -89,8 +97,19 @@ public class RecentsAnimationCallbacks implements
|
||||
RemoteAnimationTargetCompat[] appTargets,
|
||||
RemoteAnimationTargetCompat[] wallpaperTargets,
|
||||
Rect homeContentInsets, Rect minimizedHomeBounds) {
|
||||
// Convert appTargets to type RemoteAnimationTarget for all apps except Home app
|
||||
RemoteAnimationTarget[] nonHomeApps = Arrays.stream(appTargets)
|
||||
.filter(remoteAnimationTarget ->
|
||||
remoteAnimationTarget.activityType != ACTIVITY_TYPE_HOME)
|
||||
.map(RemoteAnimationTargetCompat::unwrap)
|
||||
.toArray(RemoteAnimationTarget[]::new);
|
||||
|
||||
RemoteAnimationTarget[] nonAppTargets =
|
||||
mSystemUiProxy.onGoingToRecentsLegacy(mCancelled, nonHomeApps);
|
||||
|
||||
RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
|
||||
wallpaperTargets, homeContentInsets, minimizedHomeBounds);
|
||||
wallpaperTargets, RemoteAnimationTargetCompat.wrap(nonAppTargets),
|
||||
homeContentInsets, minimizedHomeBounds);
|
||||
mController = new RecentsAnimationController(animationController,
|
||||
mAllowMinimizeSplitScreen, this::onAnimationFinished);
|
||||
|
||||
@@ -108,20 +127,20 @@ public class RecentsAnimationCallbacks implements
|
||||
|
||||
@BinderThread
|
||||
@Override
|
||||
public final void onAnimationCanceled(ThumbnailData thumbnailData) {
|
||||
public final void onAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
|
||||
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
|
||||
for (RecentsAnimationListener listener : getListeners()) {
|
||||
listener.onRecentsAnimationCanceled(thumbnailData);
|
||||
listener.onRecentsAnimationCanceled(thumbnailDatas);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@BinderThread
|
||||
@Override
|
||||
public void onTaskAppeared(RemoteAnimationTargetCompat app) {
|
||||
public void onTasksAppeared(RemoteAnimationTargetCompat[] apps) {
|
||||
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
|
||||
for (RecentsAnimationListener listener : getListeners()) {
|
||||
listener.onTaskAppeared(app);
|
||||
listener.onTasksAppeared(apps);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -149,16 +168,17 @@ public class RecentsAnimationCallbacks implements
|
||||
* Callback from the system when the recents animation is canceled. {@param thumbnailData}
|
||||
* is passed back for rendering screenshot to replace live tile.
|
||||
*/
|
||||
default void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {}
|
||||
default void onRecentsAnimationCanceled(
|
||||
@NonNull HashMap<Integer, ThumbnailData> thumbnailDatas) {}
|
||||
|
||||
/**
|
||||
* Callback made whenever the recents animation is finished.
|
||||
*/
|
||||
default void onRecentsAnimationFinished(RecentsAnimationController controller) {}
|
||||
default void onRecentsAnimationFinished(@NonNull RecentsAnimationController controller) {}
|
||||
|
||||
/**
|
||||
* Callback made when a task started from the recents is ready for an app transition.
|
||||
*/
|
||||
default void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {}
|
||||
default void onTasksAppeared(@NonNull RemoteAnimationTargetCompat[] appearedTaskTarget) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ public class RecentsAnimationController {
|
||||
private boolean mUseLauncherSysBarFlags = false;
|
||||
private boolean mSplitScreenMinimized = false;
|
||||
private boolean mFinishRequested = false;
|
||||
// Only valid when mFinishRequested == true.
|
||||
private boolean mFinishTargetIsLauncher;
|
||||
private RunnableList mPendingFinishCallbacks = new RunnableList();
|
||||
|
||||
public RecentsAnimationController(RecentsAnimationControllerCompat controller,
|
||||
@@ -145,6 +147,7 @@ public class RecentsAnimationController {
|
||||
|
||||
// Finish not yet requested
|
||||
mFinishRequested = true;
|
||||
mFinishTargetIsLauncher = toRecents;
|
||||
mOnFinishedListener.accept(this);
|
||||
mPendingFinishCallbacks.add(callback);
|
||||
UI_HELPER_EXECUTOR.execute(() -> {
|
||||
@@ -217,4 +220,12 @@ public class RecentsAnimationController {
|
||||
public RecentsAnimationControllerCompat getController() {
|
||||
return mController;
|
||||
}
|
||||
|
||||
/**
|
||||
* RecentsAnimationListeners can check this in onRecentsAnimationFinished() to determine whether
|
||||
* the animation was finished to launcher vs an app.
|
||||
*/
|
||||
public boolean getFinishTargetIsLauncher() {
|
||||
return mFinishTargetIsLauncher;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.quickstep;
|
||||
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
|
||||
import static android.content.Intent.ACTION_USER_UNLOCKED;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
|
||||
@@ -58,8 +59,6 @@ import android.os.SystemProperties;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
|
||||
@@ -67,7 +66,6 @@ import androidx.annotation.BinderThread;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.testing.TestProtocol;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
|
||||
import com.android.launcher3.util.DisplayController.Info;
|
||||
@@ -85,7 +83,6 @@ import com.android.systemui.shared.system.TaskStackChangeListeners;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Manages the state of the system during a swipe up gesture.
|
||||
@@ -147,7 +144,7 @@ public class RecentsAnimationDeviceState implements
|
||||
mContext = context;
|
||||
mDisplayController = DisplayController.INSTANCE.get(context);
|
||||
mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
|
||||
mDisplayId = mDisplayController.getInfo().id;
|
||||
mDisplayId = DEFAULT_DISPLAY;
|
||||
mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
|
||||
runOnDestroy(() -> mDisplayController.removeChangeListener(this));
|
||||
mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
|
||||
@@ -399,14 +396,6 @@ public class RecentsAnimationDeviceState implements
|
||||
&& mGestureBlockedActivities.contains(runningTaskInfo.topActivity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the packages of gesture-blocked activities.
|
||||
*/
|
||||
public List<String> getGestureBlockedActivityPackages() {
|
||||
return mGestureBlockedActivities.stream().map(ComponentName::getPackageName)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the system ui state flags from SystemUI.
|
||||
*/
|
||||
@@ -592,8 +581,7 @@ public class RecentsAnimationDeviceState implements
|
||||
final Info displayInfo = mDisplayController.getInfo();
|
||||
return (mRotationTouchHelper.touchInOneHandedModeRegion(ev)
|
||||
&& displayInfo.rotation != Surface.ROTATION_90
|
||||
&& displayInfo.rotation != Surface.ROTATION_270
|
||||
&& displayInfo.densityDpi < DisplayMetrics.DENSITY_600);
|
||||
&& displayInfo.rotation != Surface.ROTATION_270);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ public class RecentsAnimationTargets extends RemoteAnimationTargets {
|
||||
public final Rect minimizedHomeBounds;
|
||||
|
||||
public RecentsAnimationTargets(RemoteAnimationTargetCompat[] apps,
|
||||
RemoteAnimationTargetCompat[] wallpapers, Rect homeContentInsets,
|
||||
Rect minimizedHomeBounds) {
|
||||
super(apps, wallpapers, new RemoteAnimationTargetCompat[0], MODE_CLOSING);
|
||||
RemoteAnimationTargetCompat[] wallpapers, RemoteAnimationTargetCompat[] nonApps,
|
||||
Rect homeContentInsets, Rect minimizedHomeBounds) {
|
||||
super(apps, wallpapers, nonApps, MODE_CLOSING);
|
||||
this.homeContentInsets = homeContentInsets;
|
||||
this.minimizedHomeBounds = minimizedHomeBounds;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.ComponentCallbacks2;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
@@ -34,6 +35,7 @@ import com.android.launcher3.icons.IconProvider;
|
||||
import com.android.launcher3.icons.IconProvider.IconChangeListener;
|
||||
import com.android.launcher3.util.Executors.SimpleThreadFactory;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
@@ -41,6 +43,7 @@ import com.android.systemui.shared.system.KeyguardManagerCompat;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListeners;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -73,7 +76,7 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
|
||||
private RecentsModel(Context context) {
|
||||
mContext = context;
|
||||
mTaskList = new RecentTasksList(MAIN_EXECUTOR,
|
||||
new KeyguardManagerCompat(context), ActivityManagerWrapper.getInstance());
|
||||
new KeyguardManagerCompat(context), SystemUiProxy.INSTANCE.get(context));
|
||||
|
||||
IconProvider iconProvider = new LawnchairIconProvider(context);
|
||||
mIconCache = new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider);
|
||||
@@ -100,7 +103,7 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
|
||||
* always called on the UI thread.
|
||||
* @return the request id associated with this call.
|
||||
*/
|
||||
public int getTasks(Consumer<ArrayList<Task>> callback) {
|
||||
public int getTasks(Consumer<ArrayList<GroupTask>> callback) {
|
||||
return mTaskList.getTasks(false /* loadKeysOnly */, callback);
|
||||
}
|
||||
|
||||
@@ -125,9 +128,9 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
|
||||
* @param callback Receives true if task is removed, false otherwise
|
||||
*/
|
||||
public void isTaskRemoved(int taskId, Consumer<Boolean> callback) {
|
||||
mTaskList.getTasks(true /* loadKeysOnly */, (tasks) -> {
|
||||
for (Task task : tasks) {
|
||||
if (task.key.id == taskId) {
|
||||
mTaskList.getTasks(true /* loadKeysOnly */, (taskGroups) -> {
|
||||
for (GroupTask group : taskGroups) {
|
||||
if (group.containsTask(taskId)) {
|
||||
callback.accept(false);
|
||||
return;
|
||||
}
|
||||
@@ -153,14 +156,15 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
|
||||
ActivityManager.RunningTaskInfo runningTask =
|
||||
ActivityManagerWrapper.getInstance().getRunningTask();
|
||||
int runningTaskId = runningTask != null ? runningTask.id : -1;
|
||||
mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), tasks -> {
|
||||
for (Task task : tasks) {
|
||||
if (task.key.id == runningTaskId) {
|
||||
mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), taskGroups -> {
|
||||
for (GroupTask group : taskGroups) {
|
||||
if (group.containsTask(runningTaskId)) {
|
||||
// Skip the running task, it's not going to have an up-to-date snapshot by the
|
||||
// time the user next enters overview
|
||||
continue;
|
||||
}
|
||||
mThumbnailCache.updateThumbnailInCache(task);
|
||||
mThumbnailCache.updateThumbnailInCache(group.task1);
|
||||
mThumbnailCache.updateThumbnailInCache(group.task2);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -179,7 +183,7 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
|
||||
|
||||
@Override
|
||||
public void onTaskRemoved(int taskId) {
|
||||
Task.TaskKey stubKey = new Task.TaskKey(taskId, 0, null, null, 0, 0);
|
||||
Task.TaskKey stubKey = new Task.TaskKey(taskId, 0, new Intent(), null, 0, 0);
|
||||
mThumbnailCache.remove(stubKey);
|
||||
mIconCache.onTaskRemoved(stubKey);
|
||||
}
|
||||
@@ -222,6 +226,11 @@ public class RecentsModel extends TaskStackChangeListener implements IconChangeL
|
||||
mThumbnailChangeListeners.remove(listener);
|
||||
}
|
||||
|
||||
public void dump(String prefix, PrintWriter writer) {
|
||||
writer.println(prefix + "RecentsModel:");
|
||||
mTaskList.dump(" ", writer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for receiving various task properties changes
|
||||
*/
|
||||
|
||||
@@ -77,8 +77,12 @@ public class RemoteAnimationTargets {
|
||||
* Gets the navigation bar remote animation target if exists.
|
||||
*/
|
||||
public RemoteAnimationTargetCompat getNavBarRemoteAnimationTarget() {
|
||||
return getNonAppTargetOfType(TYPE_NAVIGATION_BAR);
|
||||
}
|
||||
|
||||
public RemoteAnimationTargetCompat getNonAppTargetOfType(int type) {
|
||||
for (RemoteAnimationTargetCompat target : nonApps) {
|
||||
if (target.windowType == TYPE_NAVIGATION_BAR) {
|
||||
if (target.windowType == type) {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.content.Context;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
|
||||
import com.android.quickstep.util.AnimatorControllerWithResistance;
|
||||
import com.android.quickstep.util.LauncherSplitScreenListener;
|
||||
import com.android.quickstep.util.TaskViewSimulator;
|
||||
import com.android.quickstep.util.TransformParams;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
|
||||
/**
|
||||
* Glues together the necessary components to animate a remote target using a
|
||||
* {@link TaskViewSimulator}
|
||||
*/
|
||||
public class RemoteTargetGluer {
|
||||
private RemoteTargetHandle[] mRemoteTargetHandles;
|
||||
private StagedSplitBounds mStagedSplitBounds;
|
||||
|
||||
/**
|
||||
* Use this constructor if remote targets are split-screen independent
|
||||
*/
|
||||
public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy,
|
||||
RemoteAnimationTargets targets) {
|
||||
mRemoteTargetHandles = createHandles(context, sizingStrategy, targets.apps.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this constructor if you want the number of handles created to match the number of active
|
||||
* running tasks
|
||||
*/
|
||||
public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy) {
|
||||
int[] splitIds = LauncherSplitScreenListener.INSTANCE.getNoCreate()
|
||||
.getRunningSplitTaskIds();
|
||||
mRemoteTargetHandles = createHandles(context, sizingStrategy, splitIds.length == 2 ? 2 : 1);
|
||||
}
|
||||
|
||||
private RemoteTargetHandle[] createHandles(Context context,
|
||||
BaseActivityInterface sizingStrategy, int numHandles) {
|
||||
RemoteTargetHandle[] handles = new RemoteTargetHandle[numHandles];
|
||||
for (int i = 0; i < numHandles; i++) {
|
||||
TaskViewSimulator tvs = new TaskViewSimulator(context, sizingStrategy);
|
||||
TransformParams transformParams = new TransformParams();
|
||||
handles[i] = new RemoteTargetHandle(tvs, transformParams);
|
||||
}
|
||||
return handles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pairs together {@link TaskViewSimulator}s and {@link TransformParams} into a
|
||||
* {@link RemoteTargetHandle}
|
||||
* Assigns only the apps associated with {@param targets} into their own TaskViewSimulators.
|
||||
* Length of targets.apps should match that of {@link #mRemoteTargetHandles}.
|
||||
*
|
||||
* If split screen may be active when this is called, you might want to use
|
||||
* {@link #assignTargetsForSplitScreen(RemoteAnimationTargets)}
|
||||
*/
|
||||
public RemoteTargetHandle[] assignTargets(RemoteAnimationTargets targets) {
|
||||
for (int i = 0; i < mRemoteTargetHandles.length; i++) {
|
||||
RemoteAnimationTargetCompat primaryTaskTarget = targets.apps[i];
|
||||
mRemoteTargetHandles[i].mTransformParams.setTargetSet(
|
||||
createRemoteAnimationTargetsForTarget(targets, null));
|
||||
mRemoteTargetHandles[i].mTaskViewSimulator.setPreview(primaryTaskTarget, null);
|
||||
}
|
||||
return mRemoteTargetHandles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #assignTargets(RemoteAnimationTargets)}, except this matches the
|
||||
* apps in targets.apps to that of the _active_ split screened tasks.
|
||||
* See {@link #assignTargetsForSplitScreen(RemoteAnimationTargets, int[])}
|
||||
*/
|
||||
public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets) {
|
||||
int[] splitIds = LauncherSplitScreenListener.INSTANCE.getNoCreate()
|
||||
.getRunningSplitTaskIds();
|
||||
return assignTargetsForSplitScreen(targets, splitIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the provided splitIDs to the {@link #mRemoteTargetHandles}, with index 0 will being
|
||||
* the left/top task, index 1 right/bottom
|
||||
*/
|
||||
public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets,
|
||||
int[] splitIds) {
|
||||
RemoteAnimationTargetCompat topLeftTarget; // only one set if single/fullscreen task
|
||||
RemoteAnimationTargetCompat bottomRightTarget;
|
||||
if (mRemoteTargetHandles.length == 1) {
|
||||
// If we're not in split screen, the splitIds count doesn't really matter since we
|
||||
// should always hit this case.
|
||||
mRemoteTargetHandles[0].mTransformParams.setTargetSet(targets);
|
||||
if (targets.apps.length > 0) {
|
||||
// Unclear why/when target.apps length == 0, but it sure does happen :(
|
||||
topLeftTarget = targets.apps[0];
|
||||
mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(topLeftTarget, null);
|
||||
}
|
||||
} else {
|
||||
// split screen
|
||||
topLeftTarget = targets.findTask(splitIds[0]);
|
||||
bottomRightTarget = targets.findTask(splitIds[1]);
|
||||
|
||||
// remoteTargetHandle[0] denotes topLeft task, so we pass in the bottomRight to exclude,
|
||||
// vice versa
|
||||
mStagedSplitBounds = new StagedSplitBounds(
|
||||
topLeftTarget.screenSpaceBounds,
|
||||
bottomRightTarget.screenSpaceBounds, splitIds[0], splitIds[1]);
|
||||
mRemoteTargetHandles[0].mTransformParams.setTargetSet(
|
||||
createRemoteAnimationTargetsForTarget(targets, bottomRightTarget));
|
||||
mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(topLeftTarget,
|
||||
mStagedSplitBounds);
|
||||
|
||||
mRemoteTargetHandles[1].mTransformParams.setTargetSet(
|
||||
createRemoteAnimationTargetsForTarget(targets, topLeftTarget));
|
||||
mRemoteTargetHandles[1].mTaskViewSimulator.setPreview(bottomRightTarget,
|
||||
mStagedSplitBounds);
|
||||
}
|
||||
return mRemoteTargetHandles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that we aren't excluding ancillary targets such as home/recents
|
||||
*
|
||||
* @param targetToExclude Will be excluded from the resulting return value.
|
||||
* Pass in {@code null} to not exclude anything
|
||||
* @return RemoteAnimationTargets where all the app targets from the passed in
|
||||
* {@param targets} are included except {@param targetToExclude}
|
||||
*/
|
||||
private RemoteAnimationTargets createRemoteAnimationTargetsForTarget(
|
||||
RemoteAnimationTargets targets,
|
||||
@Nullable RemoteAnimationTargetCompat targetToExclude) {
|
||||
int finalLength = targets.unfilteredApps.length - (targetToExclude == null ? 0 : 1);
|
||||
RemoteAnimationTargetCompat[] targetsWithoutExcluded =
|
||||
new RemoteAnimationTargetCompat[finalLength];
|
||||
int i = 0;
|
||||
for (RemoteAnimationTargetCompat targetCompat : targets.unfilteredApps) {
|
||||
if (targetCompat == targetToExclude) {
|
||||
continue;
|
||||
}
|
||||
targetsWithoutExcluded[i] = targetCompat;
|
||||
i++;
|
||||
}
|
||||
return new RemoteAnimationTargets(targetsWithoutExcluded,
|
||||
targets.wallpapers, targets.nonApps, targets.targetMode);
|
||||
}
|
||||
|
||||
public RemoteTargetHandle[] getRemoteTargetHandles() {
|
||||
return mRemoteTargetHandles;
|
||||
}
|
||||
|
||||
public StagedSplitBounds getStagedSplitBounds() {
|
||||
return mStagedSplitBounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Container to keep together all the associated objects whose properties need to be updated to
|
||||
* animate a single remote app target
|
||||
*/
|
||||
public static class RemoteTargetHandle {
|
||||
private final TaskViewSimulator mTaskViewSimulator;
|
||||
private final TransformParams mTransformParams;
|
||||
@Nullable
|
||||
private AnimatorControllerWithResistance mPlaybackController;
|
||||
|
||||
public RemoteTargetHandle(TaskViewSimulator taskViewSimulator,
|
||||
TransformParams transformParams) {
|
||||
mTransformParams = transformParams;
|
||||
mTaskViewSimulator = taskViewSimulator;
|
||||
}
|
||||
|
||||
public TaskViewSimulator getTaskViewSimulator() {
|
||||
return mTaskViewSimulator;
|
||||
}
|
||||
|
||||
public TransformParams getTransformParams() {
|
||||
return mTransformParams;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AnimatorControllerWithResistance getPlaybackController() {
|
||||
return mPlaybackController;
|
||||
}
|
||||
|
||||
public void setPlaybackController(
|
||||
@Nullable AnimatorControllerWithResistance playbackController) {
|
||||
mPlaybackController = playbackController;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
import static android.view.Surface.ROTATION_0;
|
||||
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
|
||||
@@ -25,7 +26,6 @@ import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.OrientationEventListener;
|
||||
|
||||
@@ -35,7 +35,6 @@ import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
|
||||
import com.android.launcher3.util.DisplayController.Info;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.quickstep.util.RecentsOrientedState;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.QuickStepContract;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListeners;
|
||||
@@ -148,7 +147,7 @@ public class RotationTouchHelper implements
|
||||
mDisplayController = DisplayController.INSTANCE.get(mContext);
|
||||
Resources resources = mContext.getResources();
|
||||
mSysUiNavMode = SysUINavigationMode.INSTANCE.get(mContext);
|
||||
mDisplayId = mDisplayController.getInfo().id;
|
||||
mDisplayId = DEFAULT_DISPLAY;
|
||||
|
||||
mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
|
||||
() -> LawnchairUtilsKt.getWindowCornerRadius(mContext));
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.DisplayController.CHANGE_ACTIVE_SCREEN;
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
|
||||
public class SimpleOrientationTouchTransformer implements
|
||||
DisplayController.DisplayInfoChangeListener {
|
||||
|
||||
public static final MainThreadInitializedObject<SimpleOrientationTouchTransformer> INSTANCE =
|
||||
new MainThreadInitializedObject<>(SimpleOrientationTouchTransformer::new);
|
||||
|
||||
private OrientationRectF mOrientationRectF;
|
||||
|
||||
public SimpleOrientationTouchTransformer(Context context) {
|
||||
DisplayController.INSTANCE.get(context).addChangeListener(this);
|
||||
onDisplayInfoChanged(context, DisplayController.INSTANCE.get(context).getInfo(),
|
||||
CHANGE_ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
|
||||
if ((flags & (CHANGE_ROTATION | CHANGE_ACTIVE_SCREEN)) == 0) {
|
||||
return;
|
||||
}
|
||||
mOrientationRectF = new OrientationRectF(0, 0, info.currentSize.y, info.currentSize.x,
|
||||
info.rotation);
|
||||
}
|
||||
|
||||
public void transform(MotionEvent ev, int rotation) {
|
||||
mOrientationRectF.applyTransformToRotation(ev, rotation, true /* forceTransform */);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ package com.android.quickstep;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_SELECT;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.content.Context;
|
||||
@@ -27,7 +27,6 @@ import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
@@ -36,28 +35,32 @@ import com.android.launcher3.anim.AnimationSuccessListener;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.touch.PagedOrientationHandler;
|
||||
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
|
||||
import com.android.quickstep.util.AnimatorControllerWithResistance;
|
||||
import com.android.quickstep.util.AppCloseConfig;
|
||||
import com.android.quickstep.util.LauncherSplitScreenListener;
|
||||
import com.android.quickstep.util.RectFSpringAnim;
|
||||
import com.android.quickstep.util.RectFSpringAnim2;
|
||||
import com.android.quickstep.util.TaskViewSimulator;
|
||||
import com.android.quickstep.util.TransformParams;
|
||||
import com.android.quickstep.util.TransformParams.BuilderProxy;
|
||||
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
|
||||
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
|
||||
|
||||
public abstract class SwipeUpAnimationLogic {
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class SwipeUpAnimationLogic implements
|
||||
RecentsAnimationCallbacks.RecentsAnimationListener{
|
||||
|
||||
protected static final Rect TEMP_RECT = new Rect();
|
||||
protected final RemoteTargetGluer mTargetGluer;
|
||||
|
||||
protected DeviceProfile mDp;
|
||||
|
||||
protected final Context mContext;
|
||||
protected final RecentsAnimationDeviceState mDeviceState;
|
||||
protected final GestureState mGestureState;
|
||||
protected final TaskViewSimulator mTaskViewSimulator;
|
||||
|
||||
protected final TransformParams mTransformParams;
|
||||
protected RemoteTargetHandle[] mRemoteTargetHandles;
|
||||
|
||||
// Shift in the range of [0, 1].
|
||||
// 0 => preview snapShot is completely visible, and hotseat is completely translated down
|
||||
@@ -70,37 +73,48 @@ public abstract class SwipeUpAnimationLogic {
|
||||
// How much further we can drag past recents, as a factor of mTransitionDragLength.
|
||||
protected float mDragLengthFactor = 1;
|
||||
|
||||
protected AnimatorControllerWithResistance mWindowTransitionController;
|
||||
protected boolean mIsSwipeForStagedSplit;
|
||||
|
||||
public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState,
|
||||
GestureState gestureState, TransformParams transformParams) {
|
||||
GestureState gestureState) {
|
||||
mContext = context;
|
||||
mDeviceState = deviceState;
|
||||
mGestureState = gestureState;
|
||||
mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface());
|
||||
mTransformParams = transformParams;
|
||||
|
||||
mTaskViewSimulator.getOrientationState().update(
|
||||
mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
|
||||
mDeviceState.getRotationTouchHelper().getDisplayRotation());
|
||||
mIsSwipeForStagedSplit = ENABLE_SPLIT_SELECT.get() &&
|
||||
LauncherSplitScreenListener.INSTANCE.getNoCreate()
|
||||
.getRunningSplitTaskIds().length > 1;
|
||||
|
||||
mTargetGluer = new RemoteTargetGluer(mContext, mGestureState.getActivityInterface());
|
||||
mRemoteTargetHandles = mTargetGluer.getRemoteTargetHandles();
|
||||
runActionOnRemoteHandles(remoteTargetHandle ->
|
||||
remoteTargetHandle.getTaskViewSimulator().getOrientationState().update(
|
||||
mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
|
||||
mDeviceState.getRotationTouchHelper().getDisplayRotation()
|
||||
));
|
||||
}
|
||||
|
||||
protected void initTransitionEndpoints(DeviceProfile dp) {
|
||||
mDp = dp;
|
||||
|
||||
mTaskViewSimulator.setDp(dp);
|
||||
mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength(
|
||||
dp, mContext, TEMP_RECT,
|
||||
mTaskViewSimulator.getOrientationState().getOrientationHandler());
|
||||
dp, mContext, TEMP_RECT, mRemoteTargetHandles[0].getTaskViewSimulator()
|
||||
.getOrientationState().getOrientationHandler());
|
||||
mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
|
||||
|
||||
PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2);
|
||||
mTaskViewSimulator.addAppToOverviewAnim(pa, LINEAR);
|
||||
AnimatorPlaybackController normalController = pa.createPlaybackController();
|
||||
mWindowTransitionController = AnimatorControllerWithResistance.createForRecents(
|
||||
normalController, mContext, mTaskViewSimulator.getOrientationState(),
|
||||
mDp, mTaskViewSimulator.recentsViewScale, AnimatedFloat.VALUE,
|
||||
mTaskViewSimulator.recentsViewSecondaryTranslation, AnimatedFloat.VALUE);
|
||||
for (RemoteTargetHandle remoteHandle : mRemoteTargetHandles) {
|
||||
PendingAnimation pendingAnimation = new PendingAnimation(mTransitionDragLength * 2);
|
||||
TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator();
|
||||
taskViewSimulator.setDp(dp);
|
||||
taskViewSimulator.addAppToOverviewAnim(pendingAnimation, LINEAR);
|
||||
AnimatorPlaybackController playbackController =
|
||||
pendingAnimation.createPlaybackController();
|
||||
|
||||
remoteHandle.setPlaybackController(AnimatorControllerWithResistance.createForRecents(
|
||||
playbackController, mContext, taskViewSimulator.getOrientationState(),
|
||||
mDp, taskViewSimulator.recentsViewScale, AnimatedFloat.VALUE,
|
||||
taskViewSimulator.recentsViewSecondaryTranslation, AnimatedFloat.VALUE
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@@ -125,7 +139,9 @@ public abstract class SwipeUpAnimationLogic {
|
||||
public abstract void updateFinalShift();
|
||||
|
||||
protected PagedOrientationHandler getOrientationHandler() {
|
||||
return mTaskViewSimulator.getOrientationState().getOrientationHandler();
|
||||
// OrientationHandler should be independent of remote target, can directly take one
|
||||
return mRemoteTargetHandles[0].getTaskViewSimulator()
|
||||
.getOrientationState().getOrientationHandler();
|
||||
}
|
||||
|
||||
protected abstract class HomeAnimationFactory {
|
||||
@@ -167,10 +183,7 @@ public abstract class SwipeUpAnimationLogic {
|
||||
|
||||
public void setAnimation(RectFSpringAnim anim) { }
|
||||
|
||||
public boolean keepWindowOpaque() { return false; }
|
||||
|
||||
public void update(@Nullable AppCloseConfig config, RectF currentRect, float progress,
|
||||
float radius) { }
|
||||
public void update(RectF currentRect, float progress, float radius) { }
|
||||
|
||||
public void onCancel() { }
|
||||
|
||||
@@ -207,16 +220,35 @@ public abstract class SwipeUpAnimationLogic {
|
||||
* @param startProgress The progress of {@link #mCurrentShift} to start thw window from.
|
||||
* @return {@link RectF} represents the bounds as starting point in window space.
|
||||
*/
|
||||
protected RectF updateProgressForStartRect(Matrix outMatrix, float startProgress) {
|
||||
protected RectF[] updateProgressForStartRect(Matrix[] outMatrix, float startProgress) {
|
||||
mCurrentShift.updateValue(startProgress);
|
||||
mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress));
|
||||
RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
|
||||
RectF[] startRects = new RectF[mRemoteTargetHandles.length];
|
||||
for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length;
|
||||
i < mRemoteTargetHandlesLength; i++) {
|
||||
RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i];
|
||||
TaskViewSimulator tvs = remoteHandle.getTaskViewSimulator();
|
||||
tvs.apply(remoteHandle.getTransformParams().setProgress(startProgress));
|
||||
|
||||
mTaskViewSimulator.applyWindowToHomeRotation(outMatrix);
|
||||
startRects[i] = new RectF(tvs.getCurrentCropRect());
|
||||
outMatrix[i] = new Matrix();
|
||||
tvs.applyWindowToHomeRotation(outMatrix[i]);
|
||||
tvs.getCurrentMatrix().mapRect(startRects[i]);
|
||||
}
|
||||
return startRects;
|
||||
}
|
||||
|
||||
final RectF startRect = new RectF(cropRectF);
|
||||
mTaskViewSimulator.getCurrentMatrix().mapRect(startRect);
|
||||
return startRect;
|
||||
/** Helper to avoid writing some for-loops to iterate over {@link #mRemoteTargetHandles} */
|
||||
protected void runActionOnRemoteHandles(Consumer<RemoteTargetHandle> consumer) {
|
||||
for (RemoteTargetHandle handle : mRemoteTargetHandles) {
|
||||
consumer.accept(handle);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return only the TaskViewSimulators from {@link #mRemoteTargetHandles} */
|
||||
protected TaskViewSimulator[] getRemoteTaskViewSimulators() {
|
||||
return Arrays.stream(mRemoteTargetHandles)
|
||||
.map(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator())
|
||||
.toArray(TaskViewSimulator[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,33 +256,41 @@ public abstract class SwipeUpAnimationLogic {
|
||||
* @param startProgress The progress of {@link #mCurrentShift} to start the window from.
|
||||
* @param homeAnimationFactory The home animation factory.
|
||||
*/
|
||||
protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
|
||||
protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress,
|
||||
HomeAnimationFactory homeAnimationFactory) {
|
||||
// TODO(b/195473584) compute separate end targets for different staged split
|
||||
final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
|
||||
RectFSpringAnim[] out = new RectFSpringAnim[mRemoteTargetHandles.length];
|
||||
Matrix[] homeToWindowPositionMap = new Matrix[mRemoteTargetHandles.length];
|
||||
RectF[] startRects = updateProgressForStartRect(homeToWindowPositionMap, startProgress);
|
||||
for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length;
|
||||
i < mRemoteTargetHandlesLength; i++) {
|
||||
RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i];
|
||||
out[i] = getWindowAnimationToHomeInternal(homeAnimationFactory,
|
||||
targetRect, remoteHandle.getTransformParams(),
|
||||
remoteHandle.getTaskViewSimulator(), startRects[i], homeToWindowPositionMap[i]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Matrix homeToWindowPositionMap = new Matrix();
|
||||
final RectF startRect = updateProgressForStartRect(homeToWindowPositionMap, startProgress);
|
||||
RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
|
||||
|
||||
private RectFSpringAnim getWindowAnimationToHomeInternal(
|
||||
HomeAnimationFactory homeAnimationFactory, RectF targetRect,
|
||||
TransformParams transformParams, TaskViewSimulator taskViewSimulator,
|
||||
RectF startRect, Matrix homeToWindowPositionMap) {
|
||||
RectF cropRectF = new RectF(taskViewSimulator.getCurrentCropRect());
|
||||
// Move the startRect to Launcher space as floatingIconView runs in Launcher
|
||||
Matrix windowToHomePositionMap = new Matrix();
|
||||
homeToWindowPositionMap.invert(windowToHomePositionMap);
|
||||
windowToHomePositionMap.mapRect(startRect);
|
||||
|
||||
RectFSpringAnim anim;
|
||||
if (PROTOTYPE_APP_CLOSE.get()) {
|
||||
anim = new RectFSpringAnim2(startRect, targetRect, mContext,
|
||||
mTaskViewSimulator.getCurrentCornerRadius(),
|
||||
homeAnimationFactory.getEndRadius(cropRectF));
|
||||
} else {
|
||||
anim = new RectFSpringAnim(startRect, targetRect, mContext);
|
||||
}
|
||||
RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext, mDp);
|
||||
homeAnimationFactory.setAnimation(anim);
|
||||
|
||||
SpringAnimationRunner runner = new SpringAnimationRunner(
|
||||
homeAnimationFactory, cropRectF, homeToWindowPositionMap);
|
||||
anim.addOnUpdateListener(runner);
|
||||
homeAnimationFactory, cropRectF, homeToWindowPositionMap,
|
||||
transformParams, taskViewSimulator);
|
||||
anim.addAnimatorListener(runner);
|
||||
anim.addOnUpdateListener(runner);
|
||||
return anim;
|
||||
}
|
||||
|
||||
@@ -262,6 +302,7 @@ public abstract class SwipeUpAnimationLogic {
|
||||
|
||||
final RectF mWindowCurrentRect = new RectF();
|
||||
final Matrix mHomeToWindowPositionMap;
|
||||
private final TransformParams mLocalTransformParams;
|
||||
final HomeAnimationFactory mAnimationFactory;
|
||||
|
||||
final AnimatorPlaybackController mHomeAnim;
|
||||
@@ -271,41 +312,36 @@ public abstract class SwipeUpAnimationLogic {
|
||||
final float mEndRadius;
|
||||
|
||||
SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF,
|
||||
Matrix homeToWindowPositionMap) {
|
||||
Matrix homeToWindowPositionMap, TransformParams transformParams,
|
||||
TaskViewSimulator taskViewSimulator) {
|
||||
mAnimationFactory = factory;
|
||||
mHomeAnim = factory.createActivityAnimationToHome();
|
||||
mCropRectF = cropRectF;
|
||||
mHomeToWindowPositionMap = homeToWindowPositionMap;
|
||||
mLocalTransformParams = transformParams;
|
||||
|
||||
cropRectF.roundOut(mCropRect);
|
||||
|
||||
// End on a "round-enough" radius so that the shape reveal doesn't have to do too much
|
||||
// rounding at the end of the animation.
|
||||
mStartRadius = mTaskViewSimulator.getCurrentCornerRadius();
|
||||
mStartRadius = taskViewSimulator.getCurrentCornerRadius();
|
||||
mEndRadius = factory.getEndRadius(cropRectF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(@Nullable AppCloseConfig config, RectF currentRect, float progress) {
|
||||
public void onUpdate(RectF currentRect, float progress) {
|
||||
mHomeAnim.setPlayFraction(progress);
|
||||
mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect);
|
||||
|
||||
mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
|
||||
float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius);
|
||||
float alpha = mAnimationFactory.getWindowAlpha(progress);
|
||||
if (config != null && PROTOTYPE_APP_CLOSE.get()) {
|
||||
alpha = config.getWindowAlpha();
|
||||
cornerRadius = config.getCornerRadius();
|
||||
}
|
||||
if (mAnimationFactory.keepWindowOpaque()) {
|
||||
alpha = 1f;
|
||||
}
|
||||
mTransformParams
|
||||
mLocalTransformParams
|
||||
.setTargetAlpha(alpha)
|
||||
.setCornerRadius(cornerRadius);
|
||||
mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this));
|
||||
mAnimationFactory.update(config, currentRect, progress,
|
||||
mMatrix.mapRadius(cornerRadius));
|
||||
mLocalTransformParams.applySurfaceParams(mLocalTransformParams
|
||||
.createSurfaceParams(this));
|
||||
mAnimationFactory.update(currentRect, progress, mMatrix.mapRadius(cornerRadius));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
@@ -33,6 +35,8 @@ import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.RemoteAnimationAdapter;
|
||||
import android.view.RemoteAnimationTarget;
|
||||
import android.view.SurfaceControl;
|
||||
|
||||
import com.android.launcher3.Utilities;
|
||||
@@ -46,11 +50,17 @@ import com.android.systemui.shared.system.smartspace.ISmartspaceTransitionContro
|
||||
import com.android.wm.shell.onehanded.IOneHanded;
|
||||
import com.android.wm.shell.pip.IPip;
|
||||
import com.android.wm.shell.pip.IPipAnimationListener;
|
||||
import com.android.wm.shell.recents.IRecentTasks;
|
||||
import com.android.wm.shell.recents.IRecentTasksListener;
|
||||
import com.android.wm.shell.splitscreen.ISplitScreen;
|
||||
import com.android.wm.shell.splitscreen.ISplitScreenListener;
|
||||
import com.android.wm.shell.startingsurface.IStartingWindow;
|
||||
import com.android.wm.shell.startingsurface.IStartingWindowListener;
|
||||
import com.android.wm.shell.transition.IShellTransitions;
|
||||
import com.android.wm.shell.util.GroupedRecentTaskInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Holds the reference to SystemUI.
|
||||
@@ -69,16 +79,21 @@ public class SystemUiProxy implements ISystemUiProxy,
|
||||
private IOneHanded mOneHanded;
|
||||
private IShellTransitions mShellTransitions;
|
||||
private IStartingWindow mStartingWindow;
|
||||
private IRecentTasks mRecentTasks;
|
||||
private final DeathRecipient mSystemUiProxyDeathRecipient = () -> {
|
||||
MAIN_EXECUTOR.execute(() -> clearProxy());
|
||||
};
|
||||
|
||||
// Save the listeners passed into the proxy since when set/register these listeners,
|
||||
// setProxy may not have been called, eg. OverviewProxyService is not connected yet.
|
||||
private IPipAnimationListener mPendingPipAnimationListener;
|
||||
private ISplitScreenListener mPendingSplitScreenListener;
|
||||
private IStartingWindowListener mPendingStartingWindowListener;
|
||||
private ISmartspaceCallback mPendingSmartspaceCallback;
|
||||
// Save the listeners passed into the proxy since OverviewProxyService may not have been bound
|
||||
// yet, and we'll need to set/register these listeners with SysUI when they do. Note that it is
|
||||
// up to the caller to clear the listeners to prevent leaks as these can be held indefinitely
|
||||
// in case SysUI needs to rebind.
|
||||
private IPipAnimationListener mPipAnimationListener;
|
||||
private ISplitScreenListener mSplitScreenListener;
|
||||
private IStartingWindowListener mStartingWindowListener;
|
||||
private ISmartspaceCallback mSmartspaceCallback;
|
||||
private IRecentTasksListener mRecentTasksListener;
|
||||
private final ArrayList<RemoteTransitionCompat> mRemoteTransitions = new ArrayList<>();
|
||||
|
||||
// Used to dedupe calls to SystemUI
|
||||
private int mLastShelfHeight;
|
||||
@@ -86,6 +101,7 @@ public class SystemUiProxy implements ISystemUiProxy,
|
||||
private float mLastNavButtonAlpha;
|
||||
private boolean mLastNavButtonAnimate;
|
||||
private boolean mHasNavButtonAlphaBeenSet = false;
|
||||
private Runnable mPendingSetNavButtonAlpha = null;
|
||||
|
||||
// TODO(141886704): Find a way to remove this
|
||||
private int mLastSystemUiStateFlags;
|
||||
@@ -111,6 +127,17 @@ public class SystemUiProxy implements ISystemUiProxy,
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImeSwitcherPressed() {
|
||||
if (mSystemUiProxy != null) {
|
||||
try {
|
||||
mSystemUiProxy.onImeSwitcherPressed();
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call onImeSwitcherPressed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHomeRotationEnabled(boolean enabled) {
|
||||
if (mSystemUiProxy != null) {
|
||||
@@ -130,7 +157,7 @@ public class SystemUiProxy implements ISystemUiProxy,
|
||||
|
||||
public void setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen,
|
||||
IOneHanded oneHanded, IShellTransitions shellTransitions,
|
||||
IStartingWindow startingWindow,
|
||||
IStartingWindow startingWindow, IRecentTasks recentTasks,
|
||||
ISmartspaceTransitionController smartSpaceTransitionController) {
|
||||
unlinkToDeath();
|
||||
mSystemUiProxy = proxy;
|
||||
@@ -140,28 +167,36 @@ public class SystemUiProxy implements ISystemUiProxy,
|
||||
mShellTransitions = shellTransitions;
|
||||
mStartingWindow = startingWindow;
|
||||
mSmartspaceTransitionController = smartSpaceTransitionController;
|
||||
mRecentTasks = recentTasks;
|
||||
linkToDeath();
|
||||
// re-attach the listeners once missing due to setProxy has not been initialized yet.
|
||||
if (mPendingPipAnimationListener != null && mPip != null) {
|
||||
setPinnedStackAnimationListener(mPendingPipAnimationListener);
|
||||
mPendingPipAnimationListener = null;
|
||||
if (mPipAnimationListener != null && mPip != null) {
|
||||
setPinnedStackAnimationListener(mPipAnimationListener);
|
||||
}
|
||||
if (mPendingSplitScreenListener != null && mSplitScreen != null) {
|
||||
registerSplitScreenListener(mPendingSplitScreenListener);
|
||||
mPendingSplitScreenListener = null;
|
||||
if (mSplitScreenListener != null && mSplitScreen != null) {
|
||||
registerSplitScreenListener(mSplitScreenListener);
|
||||
}
|
||||
if (mPendingStartingWindowListener != null && mStartingWindow != null) {
|
||||
setStartingWindowListener(mPendingStartingWindowListener);
|
||||
mPendingStartingWindowListener = null;
|
||||
if (mStartingWindowListener != null && mStartingWindow != null) {
|
||||
setStartingWindowListener(mStartingWindowListener);
|
||||
}
|
||||
if (mPendingSmartspaceCallback != null && mSmartspaceTransitionController != null) {
|
||||
setSmartspaceCallback(mPendingSmartspaceCallback);
|
||||
mPendingSmartspaceCallback = null;
|
||||
if (mSmartspaceCallback != null && mSmartspaceTransitionController != null) {
|
||||
setSmartspaceCallback(mSmartspaceCallback);
|
||||
}
|
||||
for (int i = mRemoteTransitions.size() - 1; i >= 0; --i) {
|
||||
registerRemoteTransition(mRemoteTransitions.get(i));
|
||||
}
|
||||
if (mRecentTasksListener != null && mRecentTasks != null) {
|
||||
registerRecentTasksListener(mRecentTasksListener);
|
||||
}
|
||||
|
||||
if (mPendingSetNavButtonAlpha != null) {
|
||||
mPendingSetNavButtonAlpha.run();
|
||||
mPendingSetNavButtonAlpha = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void clearProxy() {
|
||||
setProxy(null, null, null, null, null, null, null);
|
||||
setProxy(null, null, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
// TODO(141886704): Find a way to remove this
|
||||
@@ -241,14 +276,18 @@ public class SystemUiProxy implements ISystemUiProxy,
|
||||
boolean changed = Float.compare(alpha, mLastNavButtonAlpha) != 0
|
||||
|| animate != mLastNavButtonAnimate
|
||||
|| !mHasNavButtonAlphaBeenSet;
|
||||
if (mSystemUiProxy != null && changed) {
|
||||
mLastNavButtonAlpha = alpha;
|
||||
mLastNavButtonAnimate = animate;
|
||||
mHasNavButtonAlphaBeenSet = true;
|
||||
try {
|
||||
mSystemUiProxy.setNavBarButtonAlpha(alpha, animate);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call setNavBarButtonAlpha", e);
|
||||
if (changed) {
|
||||
if (mSystemUiProxy == null) {
|
||||
mPendingSetNavButtonAlpha = () -> setNavBarButtonAlpha(alpha, animate);
|
||||
} else {
|
||||
mLastNavButtonAlpha = alpha;
|
||||
mLastNavButtonAnimate = animate;
|
||||
mHasNavButtonAlphaBeenSet = true;
|
||||
try {
|
||||
mSystemUiProxy.setNavBarButtonAlpha(alpha, animate);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call setNavBarButtonAlpha", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -359,7 +398,7 @@ public class SystemUiProxy implements ISystemUiProxy,
|
||||
try {
|
||||
mSystemUiProxy.setSplitScreenMinimized(minimized);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call stopScreenPinning", e);
|
||||
Log.w(TAG, "Failed call setSplitScreenMinimized", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -406,6 +445,33 @@ public class SystemUiProxy implements ISystemUiProxy,
|
||||
// just a placeholder
|
||||
}
|
||||
|
||||
public void notifyTaskbarStatus(boolean visible, boolean stashed) {
|
||||
if (mSystemUiProxy != null) {
|
||||
try {
|
||||
mSystemUiProxy.notifyTaskbarStatus(visible, stashed);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call notifyTaskbarStatus with arg: " +
|
||||
visible + ", " + stashed, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: If called to suspend, caller MUST call this method to also un-suspend
|
||||
* @param suspend should be true to stop auto-hide, false to resume normal behavior
|
||||
*/
|
||||
@Override
|
||||
public void notifyTaskbarAutohideSuspend(boolean suspend) {
|
||||
if (mSystemUiProxy != null) {
|
||||
try {
|
||||
mSystemUiProxy.notifyTaskbarAutohideSuspend(suspend);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call notifyTaskbarAutohideSuspend with arg: " +
|
||||
suspend, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
|
||||
Insets visibleInsets, Task.TaskKey task) {
|
||||
@@ -466,9 +532,8 @@ public class SystemUiProxy implements ISystemUiProxy,
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call setPinnedStackAnimationListener", e);
|
||||
}
|
||||
} else {
|
||||
mPendingPipAnimationListener = listener;
|
||||
}
|
||||
mPipAnimationListener = listener;
|
||||
}
|
||||
|
||||
public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
|
||||
@@ -506,9 +571,8 @@ public class SystemUiProxy implements ISystemUiProxy,
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call registerSplitScreenListener");
|
||||
}
|
||||
} else {
|
||||
mPendingSplitScreenListener = listener;
|
||||
}
|
||||
mSplitScreenListener = listener;
|
||||
}
|
||||
|
||||
public void unregisterSplitScreenListener(ISplitScreenListener listener) {
|
||||
@@ -519,68 +583,44 @@ public class SystemUiProxy implements ISystemUiProxy,
|
||||
Log.w(TAG, "Failed call unregisterSplitScreenListener");
|
||||
}
|
||||
}
|
||||
mPendingSplitScreenListener = null;
|
||||
}
|
||||
|
||||
public void setSideStageVisibility(boolean visible) {
|
||||
if (mSplitScreen != null) {
|
||||
try {
|
||||
mSplitScreen.setSideStageVisibility(visible);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call setSideStageVisibility");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void exitSplitScreen() {
|
||||
if (mSplitScreen != null) {
|
||||
try {
|
||||
mSplitScreen.exitSplitScreen();
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call exitSplitScreen");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
|
||||
if (mSplitScreen != null) {
|
||||
try {
|
||||
mSplitScreen.exitSplitScreenOnHide(exitSplitScreenOnHide);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call exitSplitScreen");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startTask(int taskId, int stage, int position, Bundle options) {
|
||||
if (mSplitScreen != null) {
|
||||
try {
|
||||
mSplitScreen.startTask(taskId, stage, position, options);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call startTask");
|
||||
}
|
||||
}
|
||||
mSplitScreenListener = null;
|
||||
}
|
||||
|
||||
/** Start multiple tasks in split-screen simultaneously. */
|
||||
public void startTasks(int mainTaskId, Bundle mainOptions, int sideTaskId, Bundle sideOptions,
|
||||
@SplitConfigurationOptions.StagePosition int sidePosition,
|
||||
@SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
|
||||
RemoteTransitionCompat remoteTransition) {
|
||||
if (mSystemUiProxy != null) {
|
||||
try {
|
||||
mSplitScreen.startTasks(mainTaskId, mainOptions, sideTaskId, sideOptions,
|
||||
sidePosition, remoteTransition.getTransition());
|
||||
sidePosition, splitRatio, remoteTransition.getTransition());
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call startTask");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startShortcut(String packageName, String shortcutId, int stage, int position,
|
||||
/**
|
||||
* Start multiple tasks in split-screen simultaneously.
|
||||
*/
|
||||
public void startTasksWithLegacyTransition(int mainTaskId, Bundle mainOptions, int sideTaskId,
|
||||
Bundle sideOptions, @SplitConfigurationOptions.StagePosition int sidePosition,
|
||||
float splitRatio, RemoteAnimationAdapter adapter) {
|
||||
if (mSystemUiProxy != null) {
|
||||
try {
|
||||
mSplitScreen.startTasksWithLegacyTransition(mainTaskId, mainOptions, sideTaskId,
|
||||
sideOptions, sidePosition, splitRatio, adapter);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call startTasksWithLegacyTransition");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startShortcut(String packageName, String shortcutId, int position,
|
||||
Bundle options, UserHandle user) {
|
||||
if (mSplitScreen != null) {
|
||||
try {
|
||||
mSplitScreen.startShortcut(packageName, shortcutId, stage, position, options,
|
||||
mSplitScreen.startShortcut(packageName, shortcutId, position, options,
|
||||
user);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call startShortcut");
|
||||
@@ -588,11 +628,11 @@ public class SystemUiProxy implements ISystemUiProxy,
|
||||
}
|
||||
}
|
||||
|
||||
public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position,
|
||||
public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
|
||||
Bundle options) {
|
||||
if (mSplitScreen != null) {
|
||||
try {
|
||||
mSplitScreen.startIntent(intent, fillInIntent, stage, position, options);
|
||||
mSplitScreen.startIntent(intent, fillInIntent, position, options);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call startIntent");
|
||||
}
|
||||
@@ -609,6 +649,25 @@ public class SystemUiProxy implements ISystemUiProxy,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this when going to recents so that shell can set-up and provide appropriate leashes
|
||||
* for animation (eg. DividerBar).
|
||||
*
|
||||
* @param cancel true if recents starting is being cancelled.
|
||||
* @return RemoteAnimationTargets of windows that need to animate but only exist in shell.
|
||||
*/
|
||||
public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
|
||||
RemoteAnimationTarget[] apps) {
|
||||
if (mSplitScreen != null) {
|
||||
try {
|
||||
return mSplitScreen.onGoingToRecentsLegacy(cancel, apps);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call onGoingToRecentsLegacy");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//
|
||||
// One handed
|
||||
//
|
||||
@@ -646,6 +705,9 @@ public class SystemUiProxy implements ISystemUiProxy,
|
||||
Log.w(TAG, "Failed call registerRemoteTransition");
|
||||
}
|
||||
}
|
||||
if (!mRemoteTransitions.contains(remoteTransition)) {
|
||||
mRemoteTransitions.add(remoteTransition);
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterRemoteTransition(RemoteTransitionCompat remoteTransition) {
|
||||
@@ -656,6 +718,7 @@ public class SystemUiProxy implements ISystemUiProxy,
|
||||
Log.w(TAG, "Failed call registerRemoteTransition");
|
||||
}
|
||||
}
|
||||
mRemoteTransitions.remove(remoteTransition);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -672,12 +735,10 @@ public class SystemUiProxy implements ISystemUiProxy,
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call setStartingWindowListener", e);
|
||||
}
|
||||
} else {
|
||||
mPendingStartingWindowListener = listener;
|
||||
}
|
||||
mStartingWindowListener = listener;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// SmartSpace transitions
|
||||
//
|
||||
@@ -689,8 +750,45 @@ public class SystemUiProxy implements ISystemUiProxy,
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call setStartingWindowListener", e);
|
||||
}
|
||||
} else {
|
||||
mPendingSmartspaceCallback = callback;
|
||||
}
|
||||
mSmartspaceCallback = callback;
|
||||
}
|
||||
|
||||
//
|
||||
// Recents
|
||||
//
|
||||
|
||||
public void registerRecentTasksListener(IRecentTasksListener listener) {
|
||||
if (mRecentTasks != null) {
|
||||
try {
|
||||
mRecentTasks.registerRecentTasksListener(listener);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call registerRecentTasksListener", e);
|
||||
}
|
||||
}
|
||||
mRecentTasksListener = listener;
|
||||
}
|
||||
|
||||
public void unregisterRecentTasksListener(IRecentTasksListener listener) {
|
||||
if (mRecentTasks != null) {
|
||||
try {
|
||||
mRecentTasks.unregisterRecentTasksListener(listener);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call unregisterRecentTasksListener");
|
||||
}
|
||||
}
|
||||
mRecentTasksListener = null;
|
||||
}
|
||||
|
||||
public ArrayList<GroupedRecentTaskInfo> getRecentTasks(int numTasks, int userId) {
|
||||
if (mRecentTasks != null) {
|
||||
try {
|
||||
return new ArrayList<>(Arrays.asList(mRecentTasks.getRecentTasks(numTasks,
|
||||
RECENT_IGNORE_UNAVAILABLE, userId)));
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed call getRecentTasks", e);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ 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 static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED;
|
||||
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
@@ -27,7 +28,9 @@ import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemProperties;
|
||||
import android.util.Log;
|
||||
import android.view.RemoteAnimationTarget;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.android.launcher3.Utilities;
|
||||
@@ -41,6 +44,9 @@ import com.android.systemui.shared.system.RemoteTransitionCompat;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListeners;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
|
||||
public static final boolean ENABLE_SHELL_TRANSITIONS =
|
||||
SystemProperties.getBoolean("persist.debug.shell_transit", false);
|
||||
@@ -106,9 +112,17 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
|
||||
// But force-finish it anyways
|
||||
finishRunningRecentsAnimation(false /* toHome */);
|
||||
|
||||
if (mCallbacks != null) {
|
||||
// If mCallbacks still != null, that means we are getting this startRecentsAnimation()
|
||||
// before the previous one got onRecentsAnimationStart(). In that case, cleanup the
|
||||
// previous animation so it doesn't mess up/listen to state changes in this animation.
|
||||
cleanUpRecentsAnimation();
|
||||
}
|
||||
|
||||
final BaseActivityInterface activityInterface = gestureState.getActivityInterface();
|
||||
mLastGestureState = gestureState;
|
||||
mCallbacks = new RecentsAnimationCallbacks(activityInterface.allowMinimizeSplitScreen());
|
||||
mCallbacks = new RecentsAnimationCallbacks(SystemUiProxy.INSTANCE.get(mCtx),
|
||||
activityInterface.allowMinimizeSplitScreen());
|
||||
mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
|
||||
@Override
|
||||
public void onRecentsAnimationStart(RecentsAnimationController controller,
|
||||
@@ -126,7 +140,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
|
||||
public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
|
||||
cleanUpRecentsAnimation();
|
||||
}
|
||||
|
||||
@@ -136,18 +150,29 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
|
||||
public void onTasksAppeared(RemoteAnimationTargetCompat[] appearedTaskTargets) {
|
||||
RemoteAnimationTargetCompat appearedTaskTarget = appearedTaskTargets[0];
|
||||
BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
|
||||
// Convert appTargets to type RemoteAnimationTarget for all apps except Home app
|
||||
RemoteAnimationTarget[] nonHomeApps = Arrays.stream(appearedTaskTargets)
|
||||
.filter(remoteAnimationTarget ->
|
||||
remoteAnimationTarget.activityType != ACTIVITY_TYPE_HOME)
|
||||
.map(RemoteAnimationTargetCompat::unwrap)
|
||||
.toArray(RemoteAnimationTarget[]::new);
|
||||
|
||||
RemoteAnimationTarget[] nonAppTargets =
|
||||
SystemUiProxy.INSTANCE.getNoCreate()
|
||||
.onGoingToRecentsLegacy(false, nonHomeApps);
|
||||
|
||||
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityInterface.isInLiveTileMode()
|
||||
&& activityInterface.getCreatedActivity() != null) {
|
||||
RecentsView recentsView =
|
||||
activityInterface.getCreatedActivity().getOverviewPanel();
|
||||
if (recentsView != null) {
|
||||
RemoteAnimationTargetCompat[] apps = new RemoteAnimationTargetCompat[1];
|
||||
apps[0] = appearedTaskTarget;
|
||||
recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId, apps,
|
||||
recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId,
|
||||
appearedTaskTargets,
|
||||
new RemoteAnimationTargetCompat[0] /* wallpaper */,
|
||||
new RemoteAnimationTargetCompat[0] /* nonApps */);
|
||||
RemoteAnimationTargetCompat.wrap(nonAppTargets) /* nonApps */);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -169,7 +194,8 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
|
||||
|
||||
if (ENABLE_SHELL_TRANSITIONS) {
|
||||
RemoteTransitionCompat transition = new RemoteTransitionCompat(mCallbacks,
|
||||
mController != null ? mController.getController() : null);
|
||||
mController != null ? mController.getController() : null,
|
||||
mCtx.getIApplicationThread());
|
||||
Bundle options = ActivityOptionsCompat.makeRemoteTransition(transition)
|
||||
.setTransientLaunch().toBundle();
|
||||
mCtx.startActivity(intent, options);
|
||||
@@ -194,6 +220,22 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
|
||||
return mCallbacks;
|
||||
}
|
||||
|
||||
public void endLiveTile() {
|
||||
if (mLastGestureState == null) {
|
||||
return;
|
||||
}
|
||||
BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
|
||||
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityInterface.isInLiveTileMode()
|
||||
&& activityInterface.getCreatedActivity() != null) {
|
||||
RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
|
||||
if (recentsView != null) {
|
||||
recentsView.switchToScreenshot(null,
|
||||
() -> recentsView.finishRecentsAnimation(true /* toRecents */,
|
||||
false /* shouldPip */, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setLiveTileCleanUpHandler(Runnable cleanUpHandler) {
|
||||
mLiveTileCleanUpHandler = cleanUpHandler;
|
||||
}
|
||||
@@ -262,6 +304,11 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
|
||||
mLastAppearedTaskTarget = null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public RecentsAnimationCallbacks getCurrentCallbacks() {
|
||||
return mCallbacks;
|
||||
}
|
||||
|
||||
public void dump() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user