Merge remote-tracking branch 'aosp/android12L-release' into 12.1-dev

This commit is contained in:
Suphon Thanakornpakapong
2022-05-08 20:29:17 +07:00
825 changed files with 38324 additions and 29270 deletions
@@ -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);
}
/**
@@ -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;
}
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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;
/**
@@ -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));
@@ -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;
@@ -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();
@@ -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