Merge branch 'heads/android-14.0.0_r18' into 14-dev
This commit is contained in:
@@ -15,23 +15,16 @@
|
||||
*/
|
||||
package com.android.launcher3;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
import android.view.RemoteAnimationTarget;
|
||||
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.quickstep.util.ActivityInitListener;
|
||||
import com.android.quickstep.util.RemoteAnimationProvider;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
public class LauncherInitListener extends ActivityInitListener<Launcher> {
|
||||
|
||||
private RemoteAnimationProvider mRemoteAnimationProvider;
|
||||
|
||||
/**
|
||||
* @param onInitListener a callback made when the activity is initialized. The callback should
|
||||
* return true to continue receiving callbacks (ie. for if the activity is
|
||||
@@ -43,37 +36,7 @@ public class LauncherInitListener extends ActivityInitListener<Launcher> {
|
||||
|
||||
@Override
|
||||
public boolean handleInit(Launcher launcher, boolean alreadyOnHome) {
|
||||
if (mRemoteAnimationProvider != null) {
|
||||
QuickstepTransitionManager appTransitionManager =
|
||||
((QuickstepLauncher) launcher).getAppTransitionManager();
|
||||
|
||||
// Set a one-time animation provider. After the first call, this will get cleared.
|
||||
// TODO: Probably also check the intended target id.
|
||||
CancellationSignal cancellationSignal = new CancellationSignal();
|
||||
appTransitionManager.setRemoteAnimationProvider(new RemoteAnimationProvider() {
|
||||
@Override
|
||||
public AnimatorSet createWindowAnimation(RemoteAnimationTarget[] appTargets,
|
||||
RemoteAnimationTarget[] wallpaperTargets) {
|
||||
|
||||
// On the first call clear the reference.
|
||||
cancellationSignal.cancel();
|
||||
RemoteAnimationProvider provider = mRemoteAnimationProvider;
|
||||
mRemoteAnimationProvider = null;
|
||||
|
||||
if (provider != null && launcher.getStateManager().getState().overviewUi) {
|
||||
return provider.createWindowAnimation(appTargets, wallpaperTargets);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}, cancellationSignal);
|
||||
}
|
||||
launcher.deferOverlayCallbacksUntilNextResumeOrStop();
|
||||
return super.handleInit(launcher, alreadyOnHome);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister() {
|
||||
mRemoteAnimationProvider = null;
|
||||
super.unregister();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,12 @@ import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE;
|
||||
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
|
||||
import static android.window.TransitionFilter.CONTAINER_ORDER_TOP;
|
||||
|
||||
import static com.android.app.animation.Interpolators.ACCELERATE_1_5;
|
||||
import static com.android.app.animation.Interpolators.AGGRESSIVE_EASE;
|
||||
import static com.android.app.animation.Interpolators.DECELERATE_1_5;
|
||||
import static com.android.app.animation.Interpolators.DECELERATE_1_7;
|
||||
import static com.android.app.animation.Interpolators.EXAGGERATED_EASE;
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
|
||||
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
|
||||
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
|
||||
@@ -42,12 +48,6 @@ import static com.android.launcher3.LauncherState.BACKGROUND_APP;
|
||||
import static com.android.launcher3.LauncherState.NORMAL;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.launcher3.Utilities.mapBoundToRange;
|
||||
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;
|
||||
@@ -60,6 +60,8 @@ import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATI
|
||||
import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
|
||||
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
|
||||
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
|
||||
import static com.android.systemui.shared.system.InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME;
|
||||
import static com.android.systemui.shared.system.InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME_FALLBACK;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
|
||||
|
||||
@@ -82,7 +84,6 @@ 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;
|
||||
@@ -139,7 +140,6 @@ import com.android.quickstep.util.MultiValueUpdateListener;
|
||||
import com.android.quickstep.util.RectFSpringAnim;
|
||||
import com.android.quickstep.util.RectFSpringAnim.DefaultSpringConfig;
|
||||
import com.android.quickstep.util.RectFSpringAnim.TaskbarHotseatSpringConfig;
|
||||
import com.android.quickstep.util.RemoteAnimationProvider;
|
||||
import com.android.quickstep.util.StaggeredWorkspaceAnim;
|
||||
import com.android.quickstep.util.SurfaceTransaction;
|
||||
import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
|
||||
@@ -150,6 +150,7 @@ import com.android.quickstep.views.FloatingWidgetView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.systemui.animation.ActivityLaunchAnimator;
|
||||
import com.android.systemui.animation.DelegateLaunchAnimatorController;
|
||||
import com.android.systemui.animation.LaunchableView;
|
||||
import com.android.systemui.animation.RemoteAnimationDelegate;
|
||||
import com.android.systemui.shared.system.BlurUtils;
|
||||
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
|
||||
@@ -157,6 +158,7 @@ import com.android.systemui.shared.system.QuickStepContract;
|
||||
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
|
||||
import com.android.wm.shell.startingsurface.IStartingWindowListener;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@@ -185,7 +187,7 @@ 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 = 500;
|
||||
public 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;
|
||||
@@ -229,7 +231,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
|
||||
private DeviceProfile mDeviceProfile;
|
||||
|
||||
private RemoteAnimationProvider mRemoteAnimationProvider;
|
||||
// Strong refs to runners which are cleared when the launcher activity is
|
||||
// destroyed
|
||||
private RemoteAnimationFactory mWallpaperOpenRunner;
|
||||
@@ -288,7 +289,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
|
||||
mOpeningXInterpolator = AnimationUtils.loadInterpolator(context, R.interpolator.app_open_x);
|
||||
mOpeningInterpolator = AnimationUtils.loadInterpolator(context,
|
||||
R.interpolator.three_point_fast_out_extra_slow_in);
|
||||
R.interpolator.emphasized_interpolator);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -480,14 +481,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
return bounds;
|
||||
}
|
||||
|
||||
public void setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider,
|
||||
CancellationSignal cancellationSignal) {
|
||||
mRemoteAnimationProvider = animationProvider;
|
||||
cancellationSignal.setOnCancelListener(() -> {
|
||||
if (animationProvider == mRemoteAnimationProvider) {
|
||||
mRemoteAnimationProvider = null;
|
||||
}
|
||||
});
|
||||
/** Dump debug logs to bug report. */
|
||||
public void dump(@NonNull String prefix, @NonNull PrintWriter printWriter) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -579,7 +574,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
|
||||
ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(view, SCALE_PROPERTY, scales)
|
||||
.setDuration(CONTENT_SCALE_DURATION);
|
||||
scaleAnim.setInterpolator(DEACCEL_1_5);
|
||||
scaleAnim.setInterpolator(DECELERATE_1_5);
|
||||
launcherAnimator.play(scaleAnim);
|
||||
});
|
||||
|
||||
@@ -600,7 +595,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
ObjectAnimator scrim = ObjectAnimator.ofArgb(scrimView, VIEW_BACKGROUND_COLOR,
|
||||
colors);
|
||||
scrim.setDuration(CONTENT_SCRIM_DURATION);
|
||||
scrim.setInterpolator(DEACCEL_1_5);
|
||||
scrim.setInterpolator(DECELERATE_1_5);
|
||||
|
||||
launcherAnimator.play(scrim);
|
||||
}
|
||||
@@ -1152,7 +1147,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
* Registers remote animations used when closing apps to home screen.
|
||||
*/
|
||||
public void registerRemoteTransitions() {
|
||||
if (ENABLE_SHELL_TRANSITIONS) {
|
||||
if (ENABLE_SHELL_TRANSITIONS && Utilities.ATLEAST_U) {
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).shareTransactionQueue();
|
||||
}
|
||||
if (SEPARATE_RECENTS_ACTIVITY.get()) {
|
||||
@@ -1261,7 +1256,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
* ie. pressing home, swiping up from nav bar.
|
||||
*/
|
||||
RemoteAnimationFactory createWallpaperOpenRunner(boolean fromUnlock) {
|
||||
return new WallpaperOpenLauncherAnimationRunner(mHandler, fromUnlock);
|
||||
return new WallpaperOpenLauncherAnimationRunner(fromUnlock);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1385,7 +1380,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
* Closing animator that animates the window into its final location on the
|
||||
* workspace.
|
||||
*/
|
||||
private RectFSpringAnim getClosingWindowAnimators(AnimatorSet animation,
|
||||
protected RectFSpringAnim getClosingWindowAnimators(AnimatorSet animation,
|
||||
RemoteAnimationTarget[] targets, View launcherView, PointF velocityPxPerS,
|
||||
RectF closingWindowStartRect, float startWindowCornerRadius) {
|
||||
FloatingIconView floatingIconView = null;
|
||||
@@ -1509,11 +1504,11 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
float startShadowRadius = areAllTargetsTranslucent(appTargets) ? 0 : mMaxShadowRadius;
|
||||
closingAnimator.setDuration(duration);
|
||||
closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
|
||||
FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7);
|
||||
FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DEACCEL_1_7);
|
||||
FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DECELERATE_1_7);
|
||||
FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DECELERATE_1_7);
|
||||
FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR);
|
||||
FloatProp mShadowRadius = new FloatProp(startShadowRadius, 0, 0, duration,
|
||||
DEACCEL_1_7);
|
||||
DECELERATE_1_7);
|
||||
|
||||
@Override
|
||||
public void onUpdate(float percent, boolean initOnly) {
|
||||
@@ -1626,89 +1621,81 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
RectF startRect,
|
||||
float startWindowCornerRadius,
|
||||
boolean fromPredictiveBack) {
|
||||
AnimatorSet anim = null;
|
||||
AnimatorSet anim = new AnimatorSet();
|
||||
RectFSpringAnim rectFSpringAnim = null;
|
||||
|
||||
RemoteAnimationProvider provider = mRemoteAnimationProvider;
|
||||
if (provider != null) {
|
||||
anim = provider.createWindowAnimation(appTargets, wallpaperTargets);
|
||||
final boolean launcherIsForceInvisibleOrOpening = mLauncher.isForceInvisible()
|
||||
|| launcherIsATargetWithMode(appTargets, MODE_OPENING);
|
||||
|
||||
View launcherView = findLauncherView(appTargets);
|
||||
boolean playFallBackAnimation = (launcherView == null
|
||||
&& launcherIsForceInvisibleOrOpening)
|
||||
|| mLauncher.getWorkspace().isOverlayShown()
|
||||
|| shouldPlayFallbackClosingAnimation(appTargets);
|
||||
|
||||
boolean playWorkspaceReveal = true;
|
||||
boolean skipAllAppsScale = false;
|
||||
if (fromUnlock) {
|
||||
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);
|
||||
rectFSpringAnim = getClosingWindowAnimators(
|
||||
anim, appTargets, launcherView, velocity, startRect,
|
||||
startWindowCornerRadius);
|
||||
if (mLauncher.isInState(LauncherState.ALL_APPS)) {
|
||||
// Skip scaling all apps, otherwise FloatingIconView will get wrong
|
||||
// layout bounds.
|
||||
skipAllAppsScale = true;
|
||||
} else if (!fromPredictiveBack) {
|
||||
anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
|
||||
true /* animateOverviewScrim */, launcherView).getAnimators());
|
||||
|
||||
if (!areAllTargetsTranslucent(appTargets)) {
|
||||
anim.play(ObjectAnimator.ofFloat(mLauncher.getDepthController().stateDepth,
|
||||
MULTI_PROPERTY_VALUE,
|
||||
BACKGROUND_APP.getDepth(mLauncher), NORMAL.getDepth(mLauncher)));
|
||||
}
|
||||
|
||||
// We play StaggeredWorkspaceAnim as a part of the closing window animation.
|
||||
playWorkspaceReveal = false;
|
||||
}
|
||||
} else {
|
||||
anim.play(getFallbackClosingWindowAnimators(appTargets));
|
||||
}
|
||||
|
||||
if (anim == null) {
|
||||
anim = new AnimatorSet();
|
||||
// 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
|
||||
// contents of launcher unless we know that we are animating home as a result
|
||||
// of the home button press with quickstep, which will result in launcher being
|
||||
// started on touch down, prior to the animation home (and won't be in the
|
||||
// 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 (launcherIsForceInvisibleOrOpening) {
|
||||
addCujInstrumentation(anim, playFallBackAnimation
|
||||
? CUJ_APP_CLOSE_TO_HOME_FALLBACK
|
||||
: CUJ_APP_CLOSE_TO_HOME);
|
||||
// Only register the content animation for cancellation when state changes
|
||||
mLauncher.getStateManager().setCurrentAnimation(anim);
|
||||
|
||||
final boolean launcherIsForceInvisibleOrOpening = mLauncher.isForceInvisible()
|
||||
|| launcherIsATargetWithMode(appTargets, MODE_OPENING);
|
||||
|
||||
View launcherView = findLauncherView(appTargets);
|
||||
boolean playFallBackAnimation = (launcherView == null
|
||||
&& launcherIsForceInvisibleOrOpening)
|
||||
|| mLauncher.getWorkspace().isOverlayShown()
|
||||
|| shouldPlayFallbackClosingAnimation(appTargets);
|
||||
|
||||
boolean playWorkspaceReveal = true;
|
||||
boolean skipAllAppsScale = false;
|
||||
if (fromUnlock) {
|
||||
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);
|
||||
rectFSpringAnim = getClosingWindowAnimators(
|
||||
anim, appTargets, launcherView, velocity, startRect,
|
||||
startWindowCornerRadius);
|
||||
if (mLauncher.isInState(LauncherState.ALL_APPS)) {
|
||||
// Skip scaling all apps, otherwise FloatingIconView will get wrong
|
||||
// layout bounds.
|
||||
skipAllAppsScale = true;
|
||||
} else if (!fromPredictiveBack) {
|
||||
anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
|
||||
true /* animateOverviewScrim */, launcherView).getAnimators());
|
||||
|
||||
if (!areAllTargetsTranslucent(appTargets)) {
|
||||
anim.play(ObjectAnimator.ofFloat(mLauncher.getDepthController().stateDepth,
|
||||
MULTI_PROPERTY_VALUE,
|
||||
BACKGROUND_APP.getDepth(mLauncher), NORMAL.getDepth(mLauncher)));
|
||||
if (mLauncher.isInState(LauncherState.ALL_APPS)) {
|
||||
Pair<AnimatorSet, Runnable> contentAnimator = getLauncherContentAnimator(false,
|
||||
LAUNCHER_RESUME_START_DELAY,
|
||||
skipAllAppsScale);
|
||||
anim.play(contentAnimator.first);
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
contentAnimator.second.run();
|
||||
}
|
||||
|
||||
// We play StaggeredWorkspaceAnim as a part of the closing window animation.
|
||||
playWorkspaceReveal = false;
|
||||
}
|
||||
});
|
||||
} 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
|
||||
// contents of launcher unless we know that we are animating home as a result
|
||||
// of the home button press with quickstep, which will result in launcher being
|
||||
// started on touch down, prior to the animation home (and won't be in the
|
||||
// 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 (launcherIsForceInvisibleOrOpening) {
|
||||
addCujInstrumentation(
|
||||
anim, InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
|
||||
// Only register the content animation for cancellation when state changes
|
||||
mLauncher.getStateManager().setCurrentAnimation(anim);
|
||||
|
||||
if (mLauncher.isInState(LauncherState.ALL_APPS)) {
|
||||
Pair<AnimatorSet, Runnable> contentAnimator = getLauncherContentAnimator(false,
|
||||
LAUNCHER_RESUME_START_DELAY,
|
||||
skipAllAppsScale);
|
||||
anim.play(contentAnimator.first);
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
contentAnimator.second.run();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (playWorkspaceReveal) {
|
||||
anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators());
|
||||
}
|
||||
if (playWorkspaceReveal) {
|
||||
anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1722,11 +1709,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
*/
|
||||
protected class WallpaperOpenLauncherAnimationRunner implements RemoteAnimationFactory {
|
||||
|
||||
private final Handler mHandler;
|
||||
private final boolean mFromUnlock;
|
||||
|
||||
public WallpaperOpenLauncherAnimationRunner(Handler handler, boolean fromUnlock) {
|
||||
mHandler = handler;
|
||||
public WallpaperOpenLauncherAnimationRunner(boolean fromUnlock) {
|
||||
mFromUnlock = fromUnlock;
|
||||
}
|
||||
|
||||
@@ -1834,7 +1819,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
@Nullable
|
||||
private static ContainerAnimationRunner from(
|
||||
View v, StartingWindowListener startingWindowListener, RunnableList onEndCallback) {
|
||||
View viewToUse = findViewWithBackground(v);
|
||||
View viewToUse = findLaunchableViewWithBackground(v);
|
||||
if (viewToUse == null) {
|
||||
viewToUse = v;
|
||||
}
|
||||
@@ -1874,20 +1859,23 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the closest parent of [view] (inclusive) with a background drawable.
|
||||
* Finds the closest parent of [view] (inclusive) that implements
|
||||
* {@link LaunchableView} and
|
||||
* has a background drawable.
|
||||
*/
|
||||
@Nullable
|
||||
private static View findViewWithBackground(View view) {
|
||||
private static <T extends View & LaunchableView> T findLaunchableViewWithBackground(
|
||||
View view) {
|
||||
View current = view;
|
||||
while (current.getBackground() == null) {
|
||||
while (current.getBackground() == null || !(current instanceof LaunchableView)) {
|
||||
if (!(current.getParent() instanceof View)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
current = (View) view.getParent();
|
||||
current = (View) current.getParent();
|
||||
}
|
||||
|
||||
return current;
|
||||
return (T) current;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2087,7 +2075,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
if (progress >= end) {
|
||||
return 0f;
|
||||
}
|
||||
return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
|
||||
return Utilities.mapToRange(progress, start, end, 1, 0, ACCELERATE_1_5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorH
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.ItemInfoWithIcon;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.touch.ItemLongClickListener;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
import com.patrykmichalik.opto.core.PreferenceExtensionsKt;
|
||||
|
||||
@@ -68,7 +67,6 @@ public class PredictionRowView<T extends Context & ActivityContext>
|
||||
private FloatingHeaderView mParent;
|
||||
|
||||
private boolean mPredictionsEnabled = false;
|
||||
private OnLongClickListener mOnIconLongClickListener = ItemLongClickListener.INSTANCE_ALL_APPS;
|
||||
|
||||
private final PreferenceManager2 prefs2 = PreferenceManager2.getInstance(getContext());
|
||||
|
||||
@@ -191,15 +189,6 @@ public class PredictionRowView<T extends Context & ActivityContext>
|
||||
applyPredictionApps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the long click listener for predictions for any future predictions.
|
||||
*
|
||||
* Existing predictions in the container are not updated with this new callback.
|
||||
*/
|
||||
public void setOnIconLongClickListener(OnLongClickListener onIconLongClickListener) {
|
||||
mOnIconLongClickListener = onIconLongClickListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeviceProfileChanged(DeviceProfile dp) {
|
||||
mNumPredictedAppsPerRow = dp.numShownAllAppsColumns;
|
||||
@@ -215,9 +204,9 @@ public class PredictionRowView<T extends Context & ActivityContext>
|
||||
LayoutInflater inflater = mActivityContext.getAppsView().getLayoutInflater();
|
||||
while (getChildCount() < mNumPredictedAppsPerRow) {
|
||||
BubbleTextView icon = (BubbleTextView) inflater.inflate(
|
||||
R.layout.all_apps_icon, this, false);
|
||||
R.layout.all_apps_prediction_row_icon, this, false);
|
||||
icon.setOnClickListener(mActivityContext.getItemOnClickListener());
|
||||
icon.setOnLongClickListener(mOnIconLongClickListener);
|
||||
icon.setOnLongClickListener(mActivityContext.getAllAppsItemLongClickListener());
|
||||
icon.setLongPressTimeoutFactor(1f);
|
||||
icon.setOnFocusChangeListener(mFocusHelper);
|
||||
|
||||
|
||||
@@ -95,9 +95,7 @@ public class HotseatEduController {
|
||||
}
|
||||
}
|
||||
if (pageId == -1) {
|
||||
pageId = LauncherSettings.Settings.call(mLauncher.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
|
||||
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
|
||||
pageId = mLauncher.getModel().getModelDbController().getNewScreenId();
|
||||
mNewScreens = IntArray.wrap(pageId);
|
||||
}
|
||||
boolean isPortrait = !mLauncher.getDeviceProfile().isVerticalBarLayout();
|
||||
|
||||
@@ -19,7 +19,6 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_DENY;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_SEEN;
|
||||
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
@@ -37,7 +36,6 @@ 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.celllayout.CellLayoutLayoutParams;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.uioverrides.PredictedAppIcon;
|
||||
@@ -160,14 +158,11 @@ public class HotseatEduDialog extends AbstractSlideInView<Launcher> implements I
|
||||
}
|
||||
|
||||
private void animateOpen() {
|
||||
if (mIsOpen || mOpenCloseAnimator.isRunning()) {
|
||||
if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) {
|
||||
return;
|
||||
}
|
||||
mIsOpen = true;
|
||||
mOpenCloseAnimator.setValues(
|
||||
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
|
||||
mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
|
||||
mOpenCloseAnimator.start();
|
||||
setUpDefaultOpenAnimation().start();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,11 +22,13 @@ import static com.android.launcher3.hybridhotseat.HotseatEduController.getSettin
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.ComponentName;
|
||||
import android.util.Log;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -60,9 +62,11 @@ import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.launcher3.util.OnboardingPrefs;
|
||||
import com.android.launcher3.views.Snackbar;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -74,6 +78,7 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
SystemShortcut.Factory<QuickstepLauncher>, DeviceProfile.OnDeviceProfileChangeListener,
|
||||
DragSource, ViewGroup.OnHierarchyChangeListener {
|
||||
|
||||
private static final String TAG = "HotseatPredictionController";
|
||||
private static final int FLAG_UPDATE_PAUSED = 1 << 0;
|
||||
private static final int FLAG_DRAG_IN_PROGRESS = 1 << 1;
|
||||
private static final int FLAG_FILL_IN_PROGRESS = 1 << 2;
|
||||
@@ -114,8 +119,7 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
WorkspaceItemInfo dragItem = new WorkspaceItemInfo((WorkspaceItemInfo) v.getTag());
|
||||
v.setVisibility(View.INVISIBLE);
|
||||
mLauncher.getWorkspace().beginDragShared(
|
||||
v, null, this, dragItem, new DragPreviewProvider(v),
|
||||
mLauncher.getDefaultWorkspaceDragOptions());
|
||||
v, null, this, dragItem, new DragPreviewProvider(v), new DragOptions());
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -184,6 +188,7 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
}
|
||||
|
||||
private void fillGapsWithPrediction(boolean animate) {
|
||||
Log.d(TAG, "fillGapsWithPrediction flags: " + getStateString(mPauseFlags));
|
||||
if (mPauseFlags != 0) {
|
||||
return;
|
||||
}
|
||||
@@ -208,12 +213,16 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
View child = mHotseat.getChildAt(
|
||||
mHotseat.getCellXFromOrder(rank),
|
||||
mHotseat.getCellYFromOrder(rank));
|
||||
Log.d(TAG, "Hotseat app child is: " + child + " and isPredictedIcon() evaluates to"
|
||||
+ ": " + isPredictedIcon(child));
|
||||
|
||||
if (child != null && !isPredictedIcon(child)) {
|
||||
continue;
|
||||
}
|
||||
if (mPredictedItems.size() <= predictionIndex) {
|
||||
// Remove predicted apps from the past
|
||||
Log.d(TAG, "Remove predicted apps from the past\nPrediction Index: "
|
||||
+ predictionIndex);
|
||||
if (isPredictedIcon(child)) {
|
||||
mHotseat.removeView(child);
|
||||
}
|
||||
@@ -221,6 +230,11 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
}
|
||||
WorkspaceItemInfo predictedItem =
|
||||
(WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
|
||||
Log.d(TAG, "Predicted item is: " + predictedItem);
|
||||
if (child != null) {
|
||||
Log.d(TAG, "Predicted item is enabled: " + child.isEnabled());
|
||||
}
|
||||
|
||||
if (isPredictedIcon(child) && child.isEnabled()) {
|
||||
PredictedAppIcon icon = (PredictedAppIcon) child;
|
||||
boolean animateIconChange = icon.shouldAnimateIconChange(predictedItem);
|
||||
@@ -240,6 +254,7 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
}
|
||||
|
||||
private void bindItems(List<WorkspaceItemInfo> itemsToAdd, boolean animate) {
|
||||
Log.d(TAG, "bindItems to hotseat: " + itemsToAdd);
|
||||
AnimatorSet animationSet = new AnimatorSet();
|
||||
for (WorkspaceItemInfo item : itemsToAdd) {
|
||||
PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item);
|
||||
@@ -279,6 +294,7 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
* start and pauses predicted apps update on the hotseat
|
||||
*/
|
||||
public void setPauseUIUpdate(boolean paused) {
|
||||
Log.d(TAG, "setPauseUIUpdate parameter `paused` is " + paused);
|
||||
mPauseFlags = paused
|
||||
? (mPauseFlags | FLAG_UPDATE_PAUSED)
|
||||
: (mPauseFlags & ~FLAG_UPDATE_PAUSED);
|
||||
@@ -293,8 +309,10 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
public void setPredictedItems(FixedContainerItems items) {
|
||||
mPredictedItems = new ArrayList(items.items);
|
||||
if (mPredictedItems.isEmpty()) {
|
||||
Log.d(TAG, "Predicted items is initially empty");
|
||||
HotseatRestoreHelper.restoreBackup(mLauncher);
|
||||
}
|
||||
Log.d(TAG, "Predicted items: " + mPredictedItems);
|
||||
fillGapsWithPrediction();
|
||||
}
|
||||
|
||||
@@ -501,4 +519,21 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
&& ((WorkspaceItemInfo) view.getTag()).container
|
||||
== LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
|
||||
}
|
||||
|
||||
private static String getStateString(int flags) {
|
||||
StringJoiner str = new StringJoiner("|");
|
||||
appendFlag(str, flags, FLAG_UPDATE_PAUSED, "FLAG_UPDATE_PAUSED");
|
||||
appendFlag(str, flags, FLAG_DRAG_IN_PROGRESS, "FLAG_DRAG_IN_PROGRESS");
|
||||
appendFlag(str, flags, FLAG_FILL_IN_PROGRESS, "FLAG_FILL_IN_PROGRESS");
|
||||
appendFlag(str, flags, FLAG_REMOVING_PREDICTED_ICON,
|
||||
"FLAG_REMOVING_PREDICTED_ICON");
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
public void dump(String prefix, PrintWriter writer) {
|
||||
writer.println(prefix + this.getClass().getSimpleName());
|
||||
writer.println(prefix + "\tFlags: " + getStateString(mPauseFlags));
|
||||
writer.println(prefix + "\tmHotSeatItemsCount: " + mHotSeatItemsCount);
|
||||
writer.println(prefix + "\tmPredictedItems: " + mPredictedItems);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,10 @@ import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.model.GridBackupTable;
|
||||
import com.android.launcher3.provider.LauncherDbUtils;
|
||||
import com.android.launcher3.model.ModelDbController;
|
||||
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
|
||||
|
||||
/**
|
||||
* A helper class to manage migration revert restoration for hybrid hotseat
|
||||
@@ -36,16 +37,13 @@ public class HotseatRestoreHelper {
|
||||
*/
|
||||
public static void createBackup(Context context) {
|
||||
MODEL_EXECUTOR.execute(() -> {
|
||||
try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
|
||||
LauncherSettings.Settings.call(
|
||||
context.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
|
||||
.getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
|
||||
ModelDbController dbController = LauncherAppState.getInstance(context)
|
||||
.getModel().getModelDbController();
|
||||
try (SQLiteTransaction transaction = dbController.newTransaction()) {
|
||||
GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb());
|
||||
backupTable.createCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE);
|
||||
transaction.commit();
|
||||
LauncherSettings.Settings.call(context.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE);
|
||||
dbController.refreshHotseatRestoreTable();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -55,18 +53,15 @@ public class HotseatRestoreHelper {
|
||||
*/
|
||||
public static void restoreBackup(Context context) {
|
||||
MODEL_EXECUTOR.execute(() -> {
|
||||
try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
|
||||
LauncherSettings.Settings.call(
|
||||
context.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
|
||||
.getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
|
||||
LauncherModel model = LauncherAppState.getInstance(context).getModel();
|
||||
try (SQLiteTransaction transaction = model.getModelDbController().newTransaction()) {
|
||||
if (!tableExists(transaction.getDb(), HYBRID_HOTSEAT_BACKUP_TABLE)) {
|
||||
return;
|
||||
}
|
||||
GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb());
|
||||
backupTable.restoreFromCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE, true);
|
||||
transaction.commit();
|
||||
LauncherAppState.getInstance(context).getModel().forceReload();
|
||||
model.forceReload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -50,11 +50,12 @@ import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.util.StatsEvent;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
@@ -99,10 +100,12 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
private static final boolean IS_DEBUG = false;
|
||||
private static final String TAG = "QuickstepModelDelegate";
|
||||
|
||||
private final PredictorState mAllAppsState = new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
|
||||
private final PredictorState mHotseatState = new PredictorState(CONTAINER_HOTSEAT_PREDICTION,
|
||||
"hotseat_predictions");
|
||||
private final PredictorState mWidgetsRecommendationState = new PredictorState(CONTAINER_WIDGETS_PREDICTION,
|
||||
@VisibleForTesting
|
||||
final PredictorState mAllAppsState = new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
|
||||
@VisibleForTesting
|
||||
final PredictorState mHotseatState = new PredictorState(CONTAINER_HOTSEAT_PREDICTION, "hotseat_predictions");
|
||||
@VisibleForTesting
|
||||
final PredictorState mWidgetsRecommendationState = new PredictorState(CONTAINER_WIDGETS_PREDICTION,
|
||||
"widgets_prediction");
|
||||
|
||||
private final InvariantDeviceProfile mIDP;
|
||||
@@ -361,12 +364,7 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
.build()));
|
||||
|
||||
// TODO: get bundle
|
||||
registerPredictor(mHotseatState, apm.createAppPredictionSession(
|
||||
new AppPredictionContext.Builder(context)
|
||||
.setUiSurface("hotseat")
|
||||
.setPredictedTargetCount(mIDP.numDatabaseHotseatIcons)
|
||||
.setExtras(convertDataModelToAppTargetBundle(context, mDataModel))
|
||||
.build()));
|
||||
registerHotseatPredictor(apm, context);
|
||||
|
||||
registerWidgetsPredictor(apm.createAppPredictionSession(
|
||||
new AppPredictionContext.Builder(context)
|
||||
@@ -376,6 +374,29 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
.build()));
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void recreateHotseatPredictor() {
|
||||
mHotseatState.destroyPredictor();
|
||||
if (!mActive) {
|
||||
return;
|
||||
}
|
||||
Context context = mApp.getContext();
|
||||
AppPredictionManager apm = context.getSystemService(AppPredictionManager.class);
|
||||
if (apm == null) {
|
||||
return;
|
||||
}
|
||||
registerHotseatPredictor(apm, context);
|
||||
}
|
||||
|
||||
private void registerHotseatPredictor(AppPredictionManager apm, Context context) {
|
||||
registerPredictor(mHotseatState, apm.createAppPredictionSession(
|
||||
new AppPredictionContext.Builder(context)
|
||||
.setUiSurface("hotseat")
|
||||
.setPredictedTargetCount(mIDP.numDatabaseHotseatIcons)
|
||||
.setExtras(convertDataModelToAppTargetBundle(context, mDataModel))
|
||||
.build()));
|
||||
}
|
||||
|
||||
private void registerPredictor(PredictorState state, AppPredictor predictor) {
|
||||
state.setTargets(Collections.emptyList());
|
||||
state.predictor = predictor;
|
||||
@@ -416,7 +437,8 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
mWidgetsRecommendationState.predictor.requestPredictionUpdate();
|
||||
}
|
||||
|
||||
private void onAppTargetEvent(AppTargetEvent event, int client) {
|
||||
@VisibleForTesting
|
||||
void onAppTargetEvent(AppTargetEvent event, int client) {
|
||||
PredictorState state;
|
||||
switch (client) {
|
||||
case CONTAINER_PREDICTION:
|
||||
@@ -434,6 +456,13 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
state.predictor.notifyAppTargetEvent(event);
|
||||
Log.d(TAG, "notifyAppTargetEvent action=" + event.getAction()
|
||||
+ " launchLocation=" + event.getLaunchLocation());
|
||||
if (state == mHotseatState
|
||||
&& (event.getAction() == AppTargetEvent.ACTION_PIN
|
||||
|| event.getAction() == AppTargetEvent.ACTION_UNPIN)) {
|
||||
// Recreate hot seat predictor when we need to query for hot seat due to pin or
|
||||
// unpin app icons.
|
||||
recreateHotseatPredictor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ import android.content.IntentSender.SendIntentException;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.util.StartActivityParams;
|
||||
|
||||
public class ProxyActivityStarter extends Activity {
|
||||
|
||||
private static final String TAG = "ProxyActivityStarter";
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.proxy;
|
||||
|
||||
import static android.app.PendingIntent.FLAG_MUTABLE;
|
||||
import static android.app.PendingIntent.FLAG_ONE_SHOT;
|
||||
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.PendingIntent.CanceledException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
public class StartActivityParams implements Parcelable {
|
||||
|
||||
private static final String TAG = "StartActivityParams";
|
||||
|
||||
private final PendingIntent mPICallback;
|
||||
public final int requestCode;
|
||||
|
||||
public Intent intent;
|
||||
|
||||
public IntentSender intentSender;
|
||||
public Intent fillInIntent;
|
||||
public int flagsMask;
|
||||
public int flagsValues;
|
||||
public int extraFlags;
|
||||
public Bundle options;
|
||||
|
||||
public StartActivityParams(Activity activity, int requestCode) {
|
||||
this(activity.createPendingResult(requestCode, new Intent(),
|
||||
FLAG_ONE_SHOT | FLAG_UPDATE_CURRENT | FLAG_MUTABLE), requestCode);
|
||||
}
|
||||
|
||||
public StartActivityParams(PendingIntent pendingIntent, int requestCode) {
|
||||
this.mPICallback = pendingIntent;
|
||||
this.requestCode = requestCode;
|
||||
}
|
||||
|
||||
private StartActivityParams(Parcel parcel) {
|
||||
mPICallback = parcel.readTypedObject(PendingIntent.CREATOR);
|
||||
requestCode = parcel.readInt();
|
||||
intent = parcel.readTypedObject(Intent.CREATOR);
|
||||
|
||||
intentSender = parcel.readTypedObject(IntentSender.CREATOR);
|
||||
fillInIntent = parcel.readTypedObject(Intent.CREATOR);
|
||||
flagsMask = parcel.readInt();
|
||||
flagsValues = parcel.readInt();
|
||||
extraFlags = parcel.readInt();
|
||||
options = parcel.readBundle();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
parcel.writeTypedObject(mPICallback, flags);
|
||||
parcel.writeInt(requestCode);
|
||||
parcel.writeTypedObject(intent, flags);
|
||||
|
||||
parcel.writeTypedObject(intentSender, flags);
|
||||
parcel.writeTypedObject(fillInIntent, flags);
|
||||
parcel.writeInt(flagsMask);
|
||||
parcel.writeInt(flagsValues);
|
||||
parcel.writeInt(extraFlags);
|
||||
parcel.writeBundle(options);
|
||||
}
|
||||
|
||||
public void deliverResult(Context context, int resultCode, Intent data) {
|
||||
try {
|
||||
if (mPICallback != null) {
|
||||
mPICallback.send(context, resultCode, data);
|
||||
}
|
||||
} catch (CanceledException e) {
|
||||
Log.e(TAG, "Unable to send back result", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<StartActivityParams> CREATOR =
|
||||
new Parcelable.Creator<StartActivityParams>() {
|
||||
public StartActivityParams createFromParcel(Parcel source) {
|
||||
return new StartActivityParams(source);
|
||||
}
|
||||
|
||||
public StartActivityParams[] newArray(int size) {
|
||||
return new StartActivityParams[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
-10
@@ -18,9 +18,7 @@ package com.android.launcher3.secondarydisplay;
|
||||
import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
|
||||
import com.android.launcher3.appprediction.AppsDividerView;
|
||||
import com.android.launcher3.appprediction.PredictionRowView;
|
||||
import com.android.launcher3.model.BgDataModel;
|
||||
@@ -56,12 +54,4 @@ public final class SecondaryDisplayPredictionsImpl extends SecondaryDisplayPredi
|
||||
.findFixedRowByType(PredictionRowView.class)
|
||||
.setPredictedApps(item.items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLongClickListener(ActivityAllAppsContainerView<?> appsView,
|
||||
View.OnLongClickListener onIconLongClickListener) {
|
||||
appsView.getFloatingHeaderView()
|
||||
.findFixedRowByType(PredictionRowView.class)
|
||||
.setOnIconLongClickListener(onIconLongClickListener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package com.android.launcher3.statehandlers;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
|
||||
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
|
||||
@@ -90,10 +90,6 @@ public class DepthController extends BaseDepthController implements StateHandler
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(View view) {
|
||||
if (app.lawnchair.LawnchairApp.isAtleastT()) {
|
||||
CrossWindowBlurListeners.getInstance().removeListener(mCrossWindowBlurListener);
|
||||
}
|
||||
mLauncher.getScrimView().removeOpaquenessListener(mOpaquenessListener);
|
||||
removeSecondaryListeners();
|
||||
}
|
||||
};
|
||||
|
||||
+120
-7
@@ -15,14 +15,22 @@
|
||||
*/
|
||||
package com.android.launcher3.statehandlers;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
|
||||
import android.os.SystemProperties;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.statemanager.StatefulActivity;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.quickstep.GestureState;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.views.DesktopAppSelectView;
|
||||
import com.android.wm.shell.desktopmode.IDesktopTaskListener;
|
||||
|
||||
/**
|
||||
* Controls the visibility of the workspace and the resumed / paused state when desktop mode
|
||||
@@ -32,17 +40,68 @@ public class DesktopVisibilityController {
|
||||
|
||||
private static final String TAG = "DesktopVisController";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean(
|
||||
"persist.wm.debug.desktop_stashing", false);
|
||||
private final Launcher mLauncher;
|
||||
|
||||
private boolean mFreeformTasksVisible;
|
||||
private boolean mInOverviewState;
|
||||
private boolean mGestureInProgress;
|
||||
|
||||
@Nullable
|
||||
private IDesktopTaskListener mDesktopTaskListener;
|
||||
private DesktopAppSelectView mSelectAppToast;
|
||||
|
||||
public DesktopVisibilityController(Launcher launcher) {
|
||||
mLauncher = launcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a listener with System UI to receive updates about desktop tasks state
|
||||
*/
|
||||
public void registerSystemUiListener() {
|
||||
mDesktopTaskListener = new IDesktopTaskListener.Stub() {
|
||||
@Override
|
||||
public void onVisibilityChanged(int displayId, boolean visible) {
|
||||
MAIN_EXECUTOR.execute(() -> {
|
||||
if (displayId == mLauncher.getDisplayId()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "desktop visibility changed value=" + visible);
|
||||
}
|
||||
setFreeformTasksVisible(visible);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStashedChanged(int displayId, boolean stashed) {
|
||||
if (!IS_STASHING_ENABLED) {
|
||||
return;
|
||||
}
|
||||
MAIN_EXECUTOR.execute(() -> {
|
||||
if (displayId == mLauncher.getDisplayId()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "desktop stashed changed value=" + stashed);
|
||||
}
|
||||
if (stashed) {
|
||||
showSelectAppToast();
|
||||
} else {
|
||||
hideSelectAppToast();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(mDesktopTaskListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear listener from System UI that was set with {@link #registerSystemUiListener()}
|
||||
*/
|
||||
public void unregisterSystemUiListener() {
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether desktop mode is supported.
|
||||
*/
|
||||
@@ -68,6 +127,7 @@ public class DesktopVisibilityController {
|
||||
if (!isDesktopModeSupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (freeformTasksVisible != mFreeformTasksVisible) {
|
||||
mFreeformTasksVisible = freeformTasksVisible;
|
||||
if (mFreeformTasksVisible) {
|
||||
@@ -111,25 +171,54 @@ public class DesktopVisibilityController {
|
||||
/**
|
||||
* Whether recents gesture is currently in progress.
|
||||
*/
|
||||
public boolean isGestureInProgress() {
|
||||
public boolean isRecentsGestureInProgress() {
|
||||
return mGestureInProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether recents gesture is in progress.
|
||||
* Notify controller that recents gesture has started.
|
||||
*/
|
||||
public void setGestureInProgress(boolean gestureInProgress) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "setGestureInProgress: inProgress=" + gestureInProgress);
|
||||
}
|
||||
public void setRecentsGestureStart() {
|
||||
if (!isDesktopModeSupported()) {
|
||||
return;
|
||||
}
|
||||
setRecentsGestureInProgress(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify controller that recents gesture finished with the given
|
||||
* {@link com.android.quickstep.GestureState.GestureEndTarget}
|
||||
*/
|
||||
public void setRecentsGestureEnd(@Nullable GestureState.GestureEndTarget endTarget) {
|
||||
if (!isDesktopModeSupported()) {
|
||||
return;
|
||||
}
|
||||
setRecentsGestureInProgress(false);
|
||||
|
||||
if (endTarget == null) {
|
||||
// Gesture did not result in a new end target. Ensure launchers gets paused again.
|
||||
markLauncherPaused();
|
||||
}
|
||||
}
|
||||
|
||||
private void setRecentsGestureInProgress(boolean gestureInProgress) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "setGestureInProgress: inProgress=" + gestureInProgress);
|
||||
}
|
||||
if (gestureInProgress != mGestureInProgress) {
|
||||
mGestureInProgress = gestureInProgress;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle launcher moving to home due to home gesture or home button press.
|
||||
*/
|
||||
public void onHomeActionTriggered() {
|
||||
if (IS_STASHING_ENABLED && areFreeformTasksVisible()) {
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).stashDesktopApps(mLauncher.getDisplayId());
|
||||
}
|
||||
}
|
||||
|
||||
private void setLauncherViewsVisibility(int visibility) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility);
|
||||
@@ -168,4 +257,28 @@ public class DesktopVisibilityController {
|
||||
activity.setResumed();
|
||||
}
|
||||
}
|
||||
|
||||
private void showSelectAppToast() {
|
||||
if (mSelectAppToast != null) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "show toast to select desktop apps");
|
||||
}
|
||||
Runnable onCloseCallback = () -> {
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).hideStashedDesktopApps(mLauncher.getDisplayId());
|
||||
};
|
||||
mSelectAppToast = DesktopAppSelectView.show(mLauncher, onCloseCallback);
|
||||
}
|
||||
|
||||
private void hideSelectAppToast() {
|
||||
if (mSelectAppToast == null) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "hide toast to select desktop apps");
|
||||
}
|
||||
mSelectAppToast.hide();
|
||||
mSelectAppToast = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,13 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.statehandlers.DesktopVisibilityController;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
|
||||
import com.android.quickstep.LauncherActivityInterface;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
import com.android.quickstep.util.DesktopTask;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.quickstep.views.DesktopTaskView;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
|
||||
@@ -102,21 +106,74 @@ public final class KeyboardQuickSwitchController implements
|
||||
mQuickSwitchViewController = new KeyboardQuickSwitchViewController(
|
||||
mControllers, overlayContext, keyboardQuickSwitchView, mControllerCallbacks);
|
||||
|
||||
DesktopVisibilityController desktopController =
|
||||
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
|
||||
final boolean onDesktop =
|
||||
DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED
|
||||
&& desktopController != null
|
||||
&& desktopController.areFreeformTasksVisible();
|
||||
|
||||
if (mModel.isTaskListValid(mTaskListChangeId)) {
|
||||
mQuickSwitchViewController.openQuickSwitchView(
|
||||
mTasks, mNumHiddenTasks, /* updateTasks= */ false, currentFocusedIndex);
|
||||
mQuickSwitchViewController.openQuickSwitchView(mTasks,
|
||||
mNumHiddenTasks, /* updateTasks= */ false, currentFocusedIndex, onDesktop);
|
||||
return;
|
||||
}
|
||||
|
||||
mTaskListChangeId = mModel.getTasks((tasks) -> {
|
||||
// Only store MAX_TASK tasks, from most to least recent
|
||||
Collections.reverse(tasks);
|
||||
mTasks = tasks.stream().limit(MAX_TASKS).collect(Collectors.toList());
|
||||
mNumHiddenTasks = Math.max(0, tasks.size() - MAX_TASKS);
|
||||
mQuickSwitchViewController.openQuickSwitchView(
|
||||
mTasks, mNumHiddenTasks, /* updateTasks= */ true, currentFocusedIndex);
|
||||
if (onDesktop) {
|
||||
processLoadedTasksOnDesktop(tasks);
|
||||
} else {
|
||||
processLoadedTasks(tasks);
|
||||
}
|
||||
mQuickSwitchViewController.openQuickSwitchView(mTasks,
|
||||
mNumHiddenTasks, /* updateTasks= */ true, currentFocusedIndex, onDesktop);
|
||||
});
|
||||
}
|
||||
|
||||
private void processLoadedTasks(ArrayList<GroupTask> tasks) {
|
||||
// Only store MAX_TASK tasks, from most to least recent
|
||||
Collections.reverse(tasks);
|
||||
|
||||
// Hide all desktop tasks and show them on the hidden tile
|
||||
int hiddenDesktopTasks = 0;
|
||||
if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
|
||||
DesktopTask desktopTask = findDesktopTask(tasks);
|
||||
if (desktopTask != null) {
|
||||
hiddenDesktopTasks = desktopTask.tasks.size();
|
||||
tasks = tasks.stream()
|
||||
.filter(t -> !(t instanceof DesktopTask))
|
||||
.collect(Collectors.toCollection(ArrayList<GroupTask>::new));
|
||||
}
|
||||
}
|
||||
mTasks = tasks.stream()
|
||||
.limit(MAX_TASKS)
|
||||
.collect(Collectors.toList());
|
||||
mNumHiddenTasks = Math.max(0, tasks.size() - MAX_TASKS) + hiddenDesktopTasks;
|
||||
}
|
||||
|
||||
private void processLoadedTasksOnDesktop(ArrayList<GroupTask> tasks) {
|
||||
// Find the single desktop task that contains a grouping of desktop tasks
|
||||
DesktopTask desktopTask = findDesktopTask(tasks);
|
||||
|
||||
if (desktopTask != null) {
|
||||
mTasks = desktopTask.tasks.stream().map(GroupTask::new).collect(Collectors.toList());
|
||||
// All other tasks, apart from the grouped desktop task, are hidden
|
||||
mNumHiddenTasks = Math.max(0, tasks.size() - 1);
|
||||
} else {
|
||||
// Desktop tasks were visible, but the recents entry is missing. Fall back to empty list
|
||||
mTasks = Collections.emptyList();
|
||||
mNumHiddenTasks = tasks.size();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DesktopTask findDesktopTask(ArrayList<GroupTask> tasks) {
|
||||
return (DesktopTask) tasks.stream()
|
||||
.filter(t -> t instanceof DesktopTask)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
void closeQuickSwitchView() {
|
||||
if (mQuickSwitchViewController == null) {
|
||||
return;
|
||||
@@ -169,7 +226,7 @@ public final class KeyboardQuickSwitchController implements
|
||||
class ControllerCallbacks {
|
||||
|
||||
int getTaskCount() {
|
||||
return mNumHiddenTasks == 0 ? mTasks.size() : MAX_TASKS + 1;
|
||||
return mTasks.size() + (mNumHiddenTasks == 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -45,15 +45,22 @@ import java.util.function.Consumer;
|
||||
*/
|
||||
public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
|
||||
|
||||
@ColorInt private final int mBorderColor;
|
||||
@ColorInt
|
||||
private final int mBorderColor;
|
||||
|
||||
@Nullable private BorderAnimator mBorderAnimator;
|
||||
@Nullable
|
||||
private BorderAnimator mBorderAnimator;
|
||||
|
||||
@Nullable private ImageView mThumbnailView1;
|
||||
@Nullable private ImageView mThumbnailView2;
|
||||
@Nullable private ImageView mIcon1;
|
||||
@Nullable private ImageView mIcon2;
|
||||
@Nullable private View mContent;
|
||||
@Nullable
|
||||
private ImageView mThumbnailView1;
|
||||
@Nullable
|
||||
private ImageView mThumbnailView2;
|
||||
@Nullable
|
||||
private ImageView mIcon1;
|
||||
@Nullable
|
||||
private ImageView mIcon2;
|
||||
@Nullable
|
||||
private View mContent;
|
||||
|
||||
public KeyboardQuickSwitchTaskView(@NonNull Context context) {
|
||||
this(context, null);
|
||||
@@ -80,7 +87,7 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
|
||||
setWillNotDraw(false);
|
||||
|
||||
mBorderColor = ta.getColor(
|
||||
R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR);
|
||||
R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
|
||||
@@ -42,10 +42,10 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -53,7 +53,8 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* View that allows quick switching between recent tasks through keyboard alt-tab and alt-shift-tab
|
||||
* View that allows quick switching between recent tasks through keyboard
|
||||
* alt-tab and alt-shift-tab
|
||||
* commands.
|
||||
*/
|
||||
public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
@@ -61,10 +62,8 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
private static final long OUTLINE_ANIMATION_DURATION_MS = 333;
|
||||
private static final float OUTLINE_START_HEIGHT_FACTOR = 0.45f;
|
||||
private static final float OUTLINE_START_RADIUS_FACTOR = 0.25f;
|
||||
private static final Interpolator OPEN_OUTLINE_INTERPOLATOR =
|
||||
Interpolators.EMPHASIZED_DECELERATE;
|
||||
private static final Interpolator CLOSE_OUTLINE_INTERPOLATOR =
|
||||
Interpolators.EMPHASIZED_ACCELERATE;
|
||||
private static final Interpolator OPEN_OUTLINE_INTERPOLATOR = Interpolators.EMPHASIZED_DECELERATE;
|
||||
private static final Interpolator CLOSE_OUTLINE_INTERPOLATOR = Interpolators.EMPHASIZED_ACCELERATE;
|
||||
|
||||
private static final long ALPHA_ANIMATION_DURATION_MS = 83;
|
||||
private static final long ALPHA_ANIMATION_START_DELAY_MS = 67;
|
||||
@@ -74,10 +73,8 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
private static final float CONTENT_START_TRANSLATION_X_DP = 32;
|
||||
private static final float CONTENT_START_TRANSLATION_Y_DP = 40;
|
||||
private static final Interpolator OPEN_TRANSLATION_X_INTERPOLATOR = Interpolators.EMPHASIZED;
|
||||
private static final Interpolator OPEN_TRANSLATION_Y_INTERPOLATOR =
|
||||
Interpolators.EMPHASIZED_DECELERATE;
|
||||
private static final Interpolator CLOSE_TRANSLATION_Y_INTERPOLATOR =
|
||||
Interpolators.EMPHASIZED_ACCELERATE;
|
||||
private static final Interpolator OPEN_TRANSLATION_Y_INTERPOLATOR = Interpolators.EMPHASIZED_DECELERATE;
|
||||
private static final Interpolator CLOSE_TRANSLATION_Y_INTERPOLATOR = Interpolators.EMPHASIZED_ACCELERATE;
|
||||
|
||||
private static final long CONTENT_ALPHA_ANIMATION_DURATION_MS = 83;
|
||||
private static final long CONTENT_ALPHA_ANIMATION_START_DELAY_MS = 83;
|
||||
@@ -95,9 +92,11 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
private int mOutlineRadius;
|
||||
private boolean mIsRtl;
|
||||
|
||||
@Nullable private AnimatorSet mOpenAnimation;
|
||||
@Nullable
|
||||
private AnimatorSet mOpenAnimation;
|
||||
|
||||
@Nullable private KeyboardQuickSwitchViewController.ViewCallbacks mViewCallbacks;
|
||||
@Nullable
|
||||
private KeyboardQuickSwitchViewController.ViewCallbacks mViewCallbacks;
|
||||
|
||||
public KeyboardQuickSwitchView(@NonNull Context context) {
|
||||
this(context, null);
|
||||
@@ -159,7 +158,8 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
// Add spacing between views
|
||||
lp.setMarginStart(mSpacing);
|
||||
if (isFinalView) {
|
||||
// Add spacing to the end of the final view so that scrolling ends with some padding.
|
||||
// Add spacing to the end of the final view so that scrolling ends with some
|
||||
// padding.
|
||||
lp.endToEnd = PARENT_ID;
|
||||
lp.setMarginEnd(mSpacing);
|
||||
lp.horizontalBias = 1f;
|
||||
@@ -181,17 +181,20 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
@NonNull LayoutInflater layoutInflater,
|
||||
@Nullable View previousView,
|
||||
@NonNull String overflowString) {
|
||||
KeyboardQuickSwitchTaskView overviewButton =
|
||||
(KeyboardQuickSwitchTaskView) layoutInflater.inflate(
|
||||
R.layout.keyboard_quick_switch_overview, this, false);
|
||||
KeyboardQuickSwitchTaskView overviewButton = (KeyboardQuickSwitchTaskView) layoutInflater.inflate(
|
||||
R.layout.keyboard_quick_switch_overview, this, false);
|
||||
overviewButton.setOnClickListener(v -> mViewCallbacks.launchTappedTask(MAX_TASKS));
|
||||
|
||||
overviewButton.<TextView>findViewById(R.id.text).setText(overflowString);
|
||||
|
||||
ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
|
||||
width, mTaskViewHeight);
|
||||
lp.endToEnd = PARENT_ID;
|
||||
lp.startToEnd = previousView.getId();
|
||||
if (previousView == null) {
|
||||
lp.startToStart = PARENT_ID;
|
||||
} else {
|
||||
lp.endToEnd = PARENT_ID;
|
||||
lp.startToEnd = previousView.getId();
|
||||
}
|
||||
lp.topToTop = PARENT_ID;
|
||||
lp.bottomToBottom = PARENT_ID;
|
||||
lp.setMarginEnd(mSpacing);
|
||||
@@ -290,7 +293,8 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
|
||||
private void animateOpen(int currentFocusIndexOverride) {
|
||||
if (mOpenAnimation != null) {
|
||||
// Restart animation since currentFocusIndexOverride can change the initial scroll.
|
||||
// Restart animation since currentFocusIndexOverride can change the initial
|
||||
// scroll.
|
||||
mOpenAnimation.cancel();
|
||||
}
|
||||
mOpenAnimation = new AnimatorSet();
|
||||
@@ -404,7 +408,8 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
int firstVisibleTaskIndex = toIndex == 0
|
||||
? toIndex
|
||||
: getTaskAt(toIndex - 1) == null
|
||||
? toIndex : toIndex - 1;
|
||||
? toIndex
|
||||
: toIndex - 1;
|
||||
// Scroll so that the previous task view is truncated as a visual hint that
|
||||
// there are more tasks
|
||||
initializeScroll(
|
||||
@@ -503,9 +508,8 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
}
|
||||
|
||||
private boolean shouldScroll(@NonNull View targetTask, boolean shouldTruncateTarget) {
|
||||
boolean isTargetTruncated =
|
||||
targetTask.getRight() + mSpacing > mScrollView.getScrollX() + mScrollView.getWidth()
|
||||
|| Math.max(0, targetTask.getLeft() - mSpacing) < mScrollView.getScrollX();
|
||||
boolean isTargetTruncated = targetTask.getRight() + mSpacing > mScrollView.getScrollX() + mScrollView.getWidth()
|
||||
|| Math.max(0, targetTask.getLeft() - mSpacing) < mScrollView.getScrollX();
|
||||
|
||||
return isTargetTruncated && !shouldTruncateTarget;
|
||||
}
|
||||
@@ -513,6 +517,7 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
@Nullable
|
||||
protected KeyboardQuickSwitchTaskView getTaskAt(int index) {
|
||||
return !mDisplayingRecentTasks || index < 0 || index >= mContent.getChildCount()
|
||||
? null : (KeyboardQuickSwitchTaskView) mContent.getChildAt(index);
|
||||
? null
|
||||
: (KeyboardQuickSwitchTaskView) mContent.getChildAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import androidx.annotation.Nullable;
|
||||
import com.android.launcher3.anim.AnimationSuccessListener;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
@@ -52,6 +53,8 @@ public class KeyboardQuickSwitchViewController {
|
||||
|
||||
private int mCurrentFocusIndex = -1;
|
||||
|
||||
private boolean mOnDesktop;
|
||||
|
||||
protected KeyboardQuickSwitchViewController(
|
||||
@NonNull TaskbarControllers controllers,
|
||||
@NonNull TaskbarOverlayContext overlayContext,
|
||||
@@ -71,10 +74,12 @@ public class KeyboardQuickSwitchViewController {
|
||||
@NonNull List<GroupTask> tasks,
|
||||
int numHiddenTasks,
|
||||
boolean updateTasks,
|
||||
int currentFocusIndexOverride) {
|
||||
int currentFocusIndexOverride,
|
||||
boolean onDesktop) {
|
||||
TaskbarOverlayDragLayer dragLayer = mOverlayContext.getDragLayer();
|
||||
dragLayer.addView(mKeyboardQuickSwitchView);
|
||||
dragLayer.runOnClickOnce(v -> closeQuickSwitchView(true));
|
||||
mOnDesktop = onDesktop;
|
||||
|
||||
mKeyboardQuickSwitchView.applyLoadPlan(
|
||||
mOverlayContext,
|
||||
@@ -136,6 +141,10 @@ public class KeyboardQuickSwitchViewController {
|
||||
GroupTask task = mControllerCallbacks.getTaskAt(index);
|
||||
if (task == null) {
|
||||
return Math.max(0, index);
|
||||
} else if (mOnDesktop) {
|
||||
UI_HELPER_EXECUTOR.execute(() ->
|
||||
SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
|
||||
.showDesktopApp(task.task1.key.id));
|
||||
} else if (task.task2 == null) {
|
||||
UI_HELPER_EXECUTOR.execute(() ->
|
||||
ActivityManagerWrapper.getInstance().startActivityFromRecents(
|
||||
|
||||
@@ -317,6 +317,8 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
.getTaskbarNavButtonTranslationYForInAppDisplay()
|
||||
.updateValue(mLauncher.getDeviceProfile().getTaskbarOffsetY()
|
||||
* mTaskbarInAppDisplayProgress.value);
|
||||
mControllers.navbarButtonsViewController
|
||||
.getOnTaskbarBackgroundNavButtonColorOverride().updateValue(progress);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
|
||||
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory;
|
||||
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter;
|
||||
import com.android.launcher3.util.DimensionUtils;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
|
||||
import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
@@ -126,10 +127,11 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
private static final int FLAG_SLIDE_IN_VIEW_VISIBLE = 1 << 14;
|
||||
|
||||
/**
|
||||
* Flags where a UI could be over Taskbar surfaces, so the color override should be disabled.
|
||||
* Flags where a UI could be over Taskbar surfaces, so the color override should
|
||||
* be disabled.
|
||||
*/
|
||||
private static final int FLAGS_ON_BACKGROUND_COLOR_OVERRIDE_DISABLED =
|
||||
FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_VOICE_INTERACTION_WINDOW_SHOWING;
|
||||
private static final int FLAGS_ON_BACKGROUND_COLOR_OVERRIDE_DISABLED = FLAG_NOTIFICATION_SHADE_EXPANDED
|
||||
| FLAG_VOICE_INTERACTION_WINDOW_SHOWING;
|
||||
|
||||
private static final String NAV_BUTTONS_SEPARATE_WINDOW_TITLE = "Taskbar Nav Buttons";
|
||||
|
||||
@@ -148,9 +150,12 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
// Used for IME+A11Y buttons
|
||||
private final ViewGroup mEndContextualContainer;
|
||||
private final ViewGroup mStartContextualContainer;
|
||||
private final int mLightIconColor;
|
||||
private final int mDarkIconColor;
|
||||
/** Color to use for navigation bar buttons, if they are on on a Taskbar surface background. */
|
||||
private final int mLightIconColorOnHome;
|
||||
private final int mDarkIconColorOnHome;
|
||||
/**
|
||||
* Color to use for navigation bar buttons, if they are on on a Taskbar surface
|
||||
* background.
|
||||
*/
|
||||
private final int mOnBackgroundIconColor;
|
||||
|
||||
private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat(
|
||||
@@ -160,7 +165,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
private final AnimatedFloat mTaskbarNavButtonTranslationYForIme = new AnimatedFloat(
|
||||
this::updateNavButtonTranslationY);
|
||||
private float mLastSetNavButtonTranslationY;
|
||||
// Used for System UI state updates that should translate the nav button for in-app display.
|
||||
// Used for System UI state updates that should translate the nav button for
|
||||
// in-app display.
|
||||
private final AnimatedFloat mNavButtonInAppDisplayProgressForSysui = new AnimatedFloat(
|
||||
this::updateNavButtonInAppDisplayProgressForSysui);
|
||||
/** Expected nav button dark intensity communicated via the framework. */
|
||||
@@ -193,10 +199,10 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
// Variables for moving nav buttons to a separate window above IME
|
||||
private boolean mAreNavButtonsInSeparateWindow = false;
|
||||
private BaseDragLayer<TaskbarActivityContext> mSeparateWindowParent; // Initialized in init.
|
||||
private final ViewTreeObserver.OnComputeInternalInsetsListener mSeparateWindowInsetsComputer =
|
||||
this::onComputeInsetsForSeparateWindow;
|
||||
private final ViewTreeObserver.OnComputeInternalInsetsListener mSeparateWindowInsetsComputer = this::onComputeInsetsForSeparateWindow;
|
||||
private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
|
||||
private ImageView mRecentsButton;
|
||||
private DisplayController mDisplayController;
|
||||
|
||||
public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
|
||||
mContext = context;
|
||||
@@ -205,9 +211,11 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
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);
|
||||
mOnBackgroundIconColor = Utilities.isDarkTheme(context) ? mLightIconColor : mDarkIconColor;
|
||||
mLightIconColorOnHome = context.getColor(R.color.taskbar_nav_icon_light_color_on_home);
|
||||
mDarkIconColorOnHome = context.getColor(R.color.taskbar_nav_icon_dark_color_on_home);
|
||||
mOnBackgroundIconColor = Utilities.isDarkTheme(context)
|
||||
? context.getColor(R.color.taskbar_nav_icon_light_color)
|
||||
: context.getColor(R.color.taskbar_nav_icon_dark_color);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,7 +232,11 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
TaskbarManager.isPhoneMode(deviceProfile));
|
||||
mNavButtonsView.getLayoutParams().height = p.y;
|
||||
|
||||
mIsImeRenderingNavButtons = app.lawnchair.LawnchairApp.isAtleastT() ? InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar() : mContext.imeDrawsImeNavBar();
|
||||
mIsImeRenderingNavButtons = app.lawnchair.LawnchairApp.isAtleastT()
|
||||
? InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar()
|
||||
: mContext.imeDrawsImeNavBar();
|
||||
mDisplayController = DisplayController.INSTANCE.get(mContext);
|
||||
|
||||
if (!mIsImeRenderingNavButtons) {
|
||||
// IME switcher
|
||||
View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
|
||||
@@ -249,12 +261,14 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController
|
||||
.getKeyguardBgTaskbar(), flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0));
|
||||
|
||||
// Force nav buttons (specifically back button) to be visible during setup wizard.
|
||||
// Force nav buttons (specifically back button) to be visible during setup
|
||||
// wizard.
|
||||
boolean isInSetup = !mContext.isUserSetupComplete();
|
||||
boolean isInKidsMode = mContext.isNavBarKidsModeActive();
|
||||
boolean alwaysShowButtons = isThreeButtonNav || isInSetup;
|
||||
|
||||
// Make sure to remove nav bar buttons translation when any of the following occur:
|
||||
// Make sure to remove nav bar buttons translation when any of the following
|
||||
// occur:
|
||||
// - Notification shade is expanded
|
||||
// - IME is showing (add separate translation for IME)
|
||||
// - VoiceInteractionWindow (assistant) is showing
|
||||
@@ -266,7 +280,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
// Center nav buttons in new height for IME.
|
||||
float transForIme = (mContext.getDeviceProfile().taskbarHeight
|
||||
- mControllers.taskbarInsetsController.getTaskbarHeightForIme()) / 2f;
|
||||
// For gesture nav, nav buttons only show for IME anyway so keep them translated down.
|
||||
// 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 && !isInKidsMode, AnimatedFloat.VALUE,
|
||||
@@ -381,8 +396,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
mPropertyHolders.add(
|
||||
new StatePropertyHolder(mHomeButtonAlpha.get(
|
||||
ALPHA_INDEX_KEYGUARD_OR_DISABLE),
|
||||
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
|
||||
(flags & FLAG_DISABLE_HOME) == 0));
|
||||
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
|
||||
(flags & FLAG_DISABLE_HOME) == 0));
|
||||
|
||||
// Recents button
|
||||
mRecentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
|
||||
@@ -423,8 +438,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
|
||||
boolean isNotificationShadeExpanded = (sysUiStateFlags & shadeExpandedFlags) != 0;
|
||||
boolean isScreenPinningActive = (sysUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
|
||||
boolean isVoiceInteractionWindowShowing =
|
||||
(sysUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0;
|
||||
boolean isVoiceInteractionWindowShowing = (sysUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0;
|
||||
|
||||
// TODO(b/202218289) we're getting IME as not visible on lockscreen from system
|
||||
updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
|
||||
@@ -439,8 +453,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
|
||||
if (mA11yButton != null) {
|
||||
// Only used in 3 button
|
||||
boolean a11yLongClickable =
|
||||
(sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
|
||||
boolean a11yLongClickable = (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
|
||||
mA11yButton.setLongClickable(a11yLongClickable);
|
||||
updateButtonLayoutSpacing();
|
||||
}
|
||||
@@ -474,7 +487,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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);
|
||||
@@ -565,7 +579,10 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
return mTaskbarNavButtonTranslationY;
|
||||
}
|
||||
|
||||
/** Use to set the translationY for the all nav+contextual buttons when in Launcher */
|
||||
/**
|
||||
* Use to set the translationY for the all nav+contextual buttons when in
|
||||
* Launcher
|
||||
*/
|
||||
public AnimatedFloat getTaskbarNavButtonTranslationYForInAppDisplay() {
|
||||
return mTaskbarNavButtonTranslationYForInAppDisplay;
|
||||
}
|
||||
@@ -575,7 +592,9 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
return mTaskbarNavButtonDarkIntensity;
|
||||
}
|
||||
|
||||
/** Use to override the nav button color with {@link #mOnBackgroundIconColor}. */
|
||||
/**
|
||||
* Use to override the nav button color with {@link #mOnBackgroundIconColor}.
|
||||
*/
|
||||
public AnimatedFloat getOnTaskbarBackgroundNavButtonColorOverride() {
|
||||
return mOnTaskbarBackgroundNavButtonColorOverride;
|
||||
}
|
||||
@@ -616,10 +635,10 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
final float normalTranslationY = mTaskbarNavButtonTranslationY.value;
|
||||
final float imeAdjustmentTranslationY = mTaskbarNavButtonTranslationYForIme.value;
|
||||
TaskbarUIController uiController = mControllers.uiController;
|
||||
final float inAppDisplayAdjustmentTranslationY =
|
||||
(uiController instanceof LauncherTaskbarUIController
|
||||
&& ((LauncherTaskbarUIController) uiController).shouldUseInAppLayout())
|
||||
? mTaskbarNavButtonTranslationYForInAppDisplay.value : 0;
|
||||
final float inAppDisplayAdjustmentTranslationY = (uiController instanceof LauncherTaskbarUIController
|
||||
&& ((LauncherTaskbarUIController) uiController).shouldUseInAppLayout())
|
||||
? mTaskbarNavButtonTranslationYForInAppDisplay.value
|
||||
: 0;
|
||||
|
||||
mLastSetNavButtonTranslationY = normalTranslationY
|
||||
+ imeAdjustmentTranslationY
|
||||
@@ -629,18 +648,21 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
|
||||
private void updateNavButtonColor() {
|
||||
final ArgbEvaluator argbEvaluator = ArgbEvaluator.getInstance();
|
||||
final int sysUiNavButtonIconColor = (int) argbEvaluator.evaluate(
|
||||
final int sysUiNavButtonIconColorOnHome = (int) argbEvaluator.evaluate(
|
||||
mTaskbarNavButtonDarkIntensity.value,
|
||||
mLightIconColor,
|
||||
mDarkIconColor);
|
||||
// Override the color from framework if nav buttons are over an opaque Taskbar surface.
|
||||
mLightIconColorOnHome,
|
||||
mDarkIconColorOnHome);
|
||||
|
||||
// Override the color from framework if nav buttons are over an opaque Taskbar
|
||||
// surface.
|
||||
final int iconColor = (int) argbEvaluator.evaluate(
|
||||
mOnBackgroundNavButtonColorOverrideMultiplier.value
|
||||
* Math.max(
|
||||
mOnTaskbarBackgroundNavButtonColorOverride.value,
|
||||
mSlideInViewVisibleNavButtonColorOverride.value),
|
||||
sysUiNavButtonIconColor,
|
||||
sysUiNavButtonIconColorOnHome,
|
||||
mOnBackgroundIconColor);
|
||||
|
||||
for (ImageView button : mAllButtons) {
|
||||
button.setImageTintList(ColorStateList.valueOf(iconColor));
|
||||
}
|
||||
@@ -660,8 +682,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
buttonView.setContentDescription(parent.getContext().getString(
|
||||
navButtonController.getButtonContentDescription(buttonType)));
|
||||
buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType, view));
|
||||
buttonView.setOnLongClickListener(view ->
|
||||
navButtonController.onButtonLongClick(buttonType, view));
|
||||
buttonView.setOnLongClickListener(view -> navButtonController.onButtonLongClick(buttonType, view));
|
||||
return buttonView;
|
||||
}
|
||||
|
||||
@@ -691,8 +712,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
private void handleSetupUi() {
|
||||
// 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();
|
||||
FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams) mNavButtonContainer
|
||||
.getLayoutParams();
|
||||
Resources resources = mContext.getResources();
|
||||
DeviceProfile deviceProfile = mContext.getDeviceProfile();
|
||||
int setupMargin = resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_margin);
|
||||
@@ -703,13 +724,13 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
(resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2);
|
||||
navButtonsLayoutParams.setMarginEnd(0);
|
||||
navButtonsLayoutParams.gravity = Gravity.START;
|
||||
mNavButtonsView.getLayoutParams().height =
|
||||
mControllers.taskbarActivityContext.getSetupWindowHeight();
|
||||
mNavButtonsView.getLayoutParams().height = mControllers.taskbarActivityContext.getSetupWindowHeight();
|
||||
mNavButtonContainer.setLayoutParams(navButtonsLayoutParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the correct spacing to 3 button nav container depending on if device is in kids mode,
|
||||
* Adds the correct spacing to 3 button nav container depending on if device is
|
||||
* in kids mode,
|
||||
* setup wizard, or normal 3 button nav.
|
||||
*/
|
||||
private void updateButtonLayoutSpacing() {
|
||||
@@ -722,15 +743,11 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
boolean isInKidsMode = mContext.isNavBarKidsModeActive();
|
||||
|
||||
if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
|
||||
if (!isThreeButtonNav) {
|
||||
return;
|
||||
}
|
||||
|
||||
NavButtonLayoutter navButtonLayoutter =
|
||||
NavButtonLayoutFactory.Companion.getUiLayoutter(
|
||||
dp, mNavButtonsView, res, isInKidsMode, isInSetup, isThreeButtonNav,
|
||||
TaskbarManager.isPhoneMode(dp));
|
||||
NavButtonLayoutter navButtonLayoutter = NavButtonLayoutFactory.Companion.getUiLayoutter(
|
||||
dp, mNavButtonsView, res, isInKidsMode, isInSetup, isThreeButtonNav,
|
||||
TaskbarManager.isPhoneMode(dp), mDisplayController.getInfo().rotation);
|
||||
navButtonLayoutter.layoutButtons(dp, isContextualButtonShowing());
|
||||
updateNavButtonColor();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -772,8 +789,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
// Home button layout
|
||||
LinearLayout.LayoutParams homeLayoutparams = new LinearLayout.LayoutParams(
|
||||
buttonWidth,
|
||||
buttonHeight
|
||||
);
|
||||
buttonHeight);
|
||||
int homeButtonLeftMargin = res.getDimensionPixelSize(
|
||||
R.dimen.taskbar_home_button_left_margin_kids);
|
||||
homeLayoutparams.setMargins(homeButtonLeftMargin, 0, 0, 0);
|
||||
@@ -782,8 +798,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
// Back button layout
|
||||
LinearLayout.LayoutParams backLayoutParams = new LinearLayout.LayoutParams(
|
||||
buttonWidth,
|
||||
buttonHeight
|
||||
);
|
||||
buttonHeight);
|
||||
int backButtonLeftMargin = res.getDimensionPixelSize(
|
||||
R.dimen.taskbar_back_button_left_margin_kids);
|
||||
backLayoutParams.setMargins(backButtonLeftMargin, 0, 0, 0);
|
||||
@@ -797,8 +812,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
mBackButton.setBackground(buttonBackground);
|
||||
|
||||
// Update alignment within taskbar
|
||||
FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
|
||||
mNavButtonContainer.getLayoutParams();
|
||||
FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams) mNavButtonContainer
|
||||
.getLayoutParams();
|
||||
navButtonsLayoutParams.setMarginStart(
|
||||
navButtonsLayoutParams.getMarginEnd() / 2);
|
||||
navButtonsLayoutParams.setMarginEnd(navButtonsLayoutParams.getMarginStart());
|
||||
@@ -815,15 +830,15 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
|
||||
// Setup normal 3 button
|
||||
// Add spacing after the end of the last nav button
|
||||
FrameLayout.LayoutParams navButtonParams =
|
||||
(FrameLayout.LayoutParams) mNavButtonContainer.getLayoutParams();
|
||||
FrameLayout.LayoutParams navButtonParams = (FrameLayout.LayoutParams) mNavButtonContainer.getLayoutParams();
|
||||
navButtonParams.gravity = Gravity.END;
|
||||
navButtonParams.width = FrameLayout.LayoutParams.WRAP_CONTENT;
|
||||
navButtonParams.height = MATCH_PARENT;
|
||||
|
||||
int navMarginEnd = (int) res.getDimension(dp.inv.inlineNavButtonsEndSpacing);
|
||||
int contextualWidth = mEndContextualContainer.getWidth();
|
||||
// If contextual buttons are showing, we check if the end margin is enough for the
|
||||
// If contextual buttons are showing, we check if the end margin is enough for
|
||||
// the
|
||||
// contextual button to be showing - if not, move the nav buttons over a smidge
|
||||
if (isContextualButtonShowing() && navMarginEnd < contextualWidth) {
|
||||
// Additional spacing, eat up half of space between last icon and nav button
|
||||
@@ -836,8 +851,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
int spaceInBetween = res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween);
|
||||
for (int i = 0; i < mNavButtonContainer.getChildCount(); i++) {
|
||||
View navButton = mNavButtonContainer.getChildAt(i);
|
||||
LinearLayout.LayoutParams buttonLayoutParams =
|
||||
(LinearLayout.LayoutParams) navButton.getLayoutParams();
|
||||
LinearLayout.LayoutParams buttonLayoutParams = (LinearLayout.LayoutParams) navButton.getLayoutParams();
|
||||
buttonLayoutParams.weight = 0;
|
||||
if (i == 0) {
|
||||
buttonLayoutParams.setMarginEnd(spaceInBetween / 2);
|
||||
@@ -867,7 +881,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves mNavButtonsView from TaskbarDragLayer to a placeholder BaseDragLayer on a new window.
|
||||
* Moves mNavButtonsView from TaskbarDragLayer to a placeholder BaseDragLayer on
|
||||
* a new window.
|
||||
*/
|
||||
public void moveNavButtonsToNewWindow() {
|
||||
if (mAreNavButtonsInSeparateWindow) {
|
||||
@@ -875,7 +890,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
}
|
||||
|
||||
if (mIsImeRenderingNavButtons) {
|
||||
// IME is rendering the nav buttons, so we don't need to create a new layer for them.
|
||||
// IME is rendering the nav buttons, so we don't need to create a new layer for
|
||||
// them.
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -904,7 +920,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves mNavButtonsView from its temporary window and reattaches it to TaskbarDragLayer.
|
||||
* Moves mNavButtonsView from its temporary window and reattaches it to
|
||||
* TaskbarDragLayer.
|
||||
*/
|
||||
public void moveNavButtonsBackToTaskbarWindow() {
|
||||
if (!mAreNavButtonsInSeparateWindow) {
|
||||
@@ -923,7 +940,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a new ui controller is set, and should update anything that depends on the
|
||||
* Called whenever a new ui controller is set, and should update anything that
|
||||
* depends on the
|
||||
* ui controller.
|
||||
*/
|
||||
public void onUiControllerChanged() {
|
||||
@@ -936,8 +954,6 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
pw.println(prefix + "NavbarButtonsViewController:");
|
||||
|
||||
pw.println(prefix + "\tmState=" + getStateString(mState));
|
||||
pw.println(prefix + "\tmLightIconColor=" + Integer.toHexString(mLightIconColor));
|
||||
pw.println(prefix + "\tmDarkIconColor=" + Integer.toHexString(mDarkIconColor));
|
||||
pw.println(prefix + "\tmFloatingRotationButtonBounds=" + mFloatingRotationButtonBounds);
|
||||
pw.println(prefix + "\tmSysuiStateFlags=" + QuickStepContract.getSystemUiStateString(
|
||||
mSysuiStateFlags));
|
||||
@@ -948,6 +964,14 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
+ mTaskbarNavButtonTranslationYForInAppDisplay.value);
|
||||
pw.println(prefix + "\t\tmTaskbarNavButtonTranslationYForIme="
|
||||
+ mTaskbarNavButtonTranslationYForIme.value);
|
||||
pw.println(prefix + "\t\tmTaskbarNavButtonDarkIntensity="
|
||||
+ mTaskbarNavButtonDarkIntensity.value);
|
||||
pw.println(prefix + "\t\tmSlideInViewVisibleNavButtonColorOverride="
|
||||
+ mSlideInViewVisibleNavButtonColorOverride.value);
|
||||
pw.println(prefix + "\t\tmOnTaskbarBackgroundNavButtonColorOverride="
|
||||
+ mOnTaskbarBackgroundNavButtonColorOverride.value);
|
||||
pw.println(prefix + "\t\tmOnBackgroundNavButtonColorOverrideMultiplier="
|
||||
+ mOnBackgroundNavButtonColorOverrideMultiplier.value);
|
||||
}
|
||||
|
||||
private static String getStateString(int flags) {
|
||||
|
||||
@@ -49,14 +49,15 @@ import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.os.Process;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.Trace;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.Gravity;
|
||||
import android.view.RoundedCorner;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.Toast;
|
||||
@@ -70,6 +71,7 @@ import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.dot.DotInfo;
|
||||
@@ -90,6 +92,8 @@ import com.android.launcher3.taskbar.bubbles.BubbleBarController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarView;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleDismissController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleDragController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleStashController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
|
||||
@@ -117,6 +121,7 @@ import com.android.systemui.unfold.updates.RotationChangeProvider;
|
||||
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -128,8 +133,6 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
|
||||
private static final String IME_DRAWS_IME_NAV_BAR_RES_NAME = "config_imeDrawsImeNavBar";
|
||||
|
||||
private static final boolean ENABLE_THREE_BUTTON_TASKBAR =
|
||||
SystemProperties.getBoolean("persist.debug.taskbar_three_button", false);
|
||||
private static final String TAG = "TaskbarActivityContext";
|
||||
|
||||
private static final String WINDOW_TITLE = "Taskbar";
|
||||
@@ -146,7 +149,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
private int mLastRequestedNonFullscreenHeight;
|
||||
|
||||
private NavigationMode mNavMode;
|
||||
private final boolean mImeDrawsImeNavBar;
|
||||
private boolean mImeDrawsImeNavBar;
|
||||
private final ViewCache mViewCache = new ViewCache();
|
||||
|
||||
private final boolean mIsSafeModeEnabled;
|
||||
@@ -169,30 +172,27 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
|
||||
unfoldTransitionProgressProvider) {
|
||||
super(windowContext);
|
||||
|
||||
applyDeviceProfile(launcherDp);
|
||||
|
||||
final Resources resources = getResources();
|
||||
|
||||
matchDeviceProfile(launcherDp, getResources());
|
||||
|
||||
mNavMode = DisplayController.getNavigationMode(windowContext);
|
||||
mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
|
||||
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
|
||||
() -> getPackageManager().isSafeMode());
|
||||
|
||||
// TODO(b/244231596) For shared Taskbar window, update this value in applyDeviceProfile()
|
||||
// instead so to get correct value when recreating the taskbar
|
||||
SettingsCache settingsCache = SettingsCache.INSTANCE.get(this);
|
||||
mIsUserSetupComplete = settingsCache.getValue(
|
||||
Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
|
||||
mIsNavBarForceVisible = settingsCache.getValue(
|
||||
Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
|
||||
|
||||
// TODO(b/244231596) For shared Taskbar window, update this value in init() instead so
|
||||
// to get correct value when recreating the taskbar
|
||||
mIsNavBarKidsMode = settingsCache.getValue(
|
||||
Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
|
||||
mIsNavBarForceVisible = mIsNavBarKidsMode;
|
||||
|
||||
// Get display and corners first, as views might use them in constructor.
|
||||
Display display = windowContext.getDisplay();
|
||||
Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
|
||||
? windowContext.getApplicationContext()
|
||||
: windowContext.getApplicationContext().createDisplayContext(display);
|
||||
Context c = getApplicationContext();
|
||||
mWindowManager = c.getSystemService(WindowManager.class);
|
||||
mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
|
||||
mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
|
||||
@@ -216,29 +216,34 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
|
||||
// If Bubble bar is present, TaskbarControllers depends on it so build it first.
|
||||
Optional<BubbleControllers> bubbleControllersOptional = Optional.empty();
|
||||
if (BubbleBarController.BUBBLE_BAR_ENABLED) {
|
||||
if (BubbleBarController.BUBBLE_BAR_ENABLED && bubbleBarView != null) {
|
||||
bubbleControllersOptional = Optional.of(new BubbleControllers(
|
||||
new BubbleBarController(this, bubbleBarView),
|
||||
new BubbleBarViewController(this, bubbleBarView),
|
||||
new BubbleStashController(this),
|
||||
new BubbleStashedHandleViewController(this, bubbleHandleView)));
|
||||
new BubbleStashedHandleViewController(this, bubbleHandleView),
|
||||
new BubbleDragController(this),
|
||||
new BubbleDismissController(this, mDragLayer)));
|
||||
}
|
||||
|
||||
// Construct controllers.
|
||||
RotationButtonController rotationButtonController = new RotationButtonController(this,
|
||||
c.getColor(R.color.floating_rotation_button_light_color),
|
||||
c.getColor(R.color.floating_rotation_button_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());
|
||||
rotationButtonController.setBgExecutor(Executors.THREAD_POOL_EXECUTOR);
|
||||
|
||||
mControllers = new TaskbarControllers(this,
|
||||
new TaskbarDragController(this),
|
||||
buttonController,
|
||||
isDesktopMode
|
||||
? new DesktopNavbarButtonsViewController(this, navButtonsView)
|
||||
: new NavbarButtonsViewController(this, navButtonsView),
|
||||
new RotationButtonController(this,
|
||||
c.getColor(R.color.floating_rotation_button_light_color),
|
||||
c.getColor(R.color.floating_rotation_button_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()),
|
||||
rotationButtonController,
|
||||
new TaskbarDragLayerController(this, mDragLayer),
|
||||
new TaskbarViewController(this, taskbarView),
|
||||
new TaskbarScrimViewController(this, taskbarScrimView),
|
||||
@@ -267,13 +272,48 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
bubbleControllersOptional);
|
||||
}
|
||||
|
||||
/** Updates {@link DeviceProfile} instances for any Taskbar windows. */
|
||||
public void updateDeviceProfile(DeviceProfile launcherDp) {
|
||||
applyDeviceProfile(launcherDp);
|
||||
|
||||
mControllers.taskbarOverlayController.updateLauncherDeviceProfile(launcherDp);
|
||||
AbstractFloatingView.closeAllOpenViewsExcept(this, false, TYPE_REBIND_SAFE);
|
||||
// Reapply fullscreen to take potential new screen size into account.
|
||||
setTaskbarWindowFullscreen(mIsFullscreen);
|
||||
|
||||
dispatchDeviceProfileChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the original DeviceProfile, match the number of hotseat icons and qsb width and update
|
||||
* the icon size
|
||||
*/
|
||||
private void applyDeviceProfile(DeviceProfile originDeviceProfile) {
|
||||
mDeviceProfile = originDeviceProfile.toBuilder(this)
|
||||
.withDimensionsOverride(deviceProfile -> {
|
||||
// Taskbar should match the number of icons of hotseat
|
||||
deviceProfile.numShownHotseatIcons = originDeviceProfile.numShownHotseatIcons;
|
||||
// Same QSB width to have a smooth animation
|
||||
deviceProfile.hotseatQsbWidth = originDeviceProfile.hotseatQsbWidth;
|
||||
|
||||
// Update icon size
|
||||
deviceProfile.iconSizePx = deviceProfile.taskbarIconSize;
|
||||
deviceProfile.updateIconSize(1f, getResources());
|
||||
}).build();
|
||||
mNavMode = DisplayController.getNavigationMode(this);
|
||||
}
|
||||
|
||||
|
||||
public void init(@NonNull TaskbarSharedState sharedState) {
|
||||
mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, getResources(), false);
|
||||
mLastRequestedNonFullscreenHeight = getDefaultTaskbarWindowHeight();
|
||||
mWindowLayoutParams =
|
||||
createDefaultWindowLayoutParams(TYPE_NAVIGATION_BAR_PANEL, WINDOW_TITLE);
|
||||
mWindowLayoutParams = createAllWindowParams();
|
||||
|
||||
// Initialize controllers after all are constructed.
|
||||
mControllers.init(sharedState);
|
||||
// This may not be necessary and can be reverted once we move towards recreating all
|
||||
// controllers without re-creating the window
|
||||
mControllers.rotationButtonController.onNavigationModeChanged(mNavMode.resValue);
|
||||
updateSysuiStateFlags(sharedState.sysuiStateFlags, true /* fromInit */);
|
||||
disableNavBarElements(sharedState.disableNavBarDisplayId, sharedState.disableNavBarState1,
|
||||
sharedState.disableNavBarState2, false /* animate */);
|
||||
@@ -303,47 +343,21 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
mControllers.taskbarStashController.showTaskbarFromBroadcast();
|
||||
}
|
||||
|
||||
/** Toggles Taskbar All Apps overlay. */
|
||||
public void toggleAllApps() {
|
||||
mControllers.taskbarAllAppsController.toggle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceProfile getDeviceProfile() {
|
||||
return mDeviceProfile;
|
||||
}
|
||||
|
||||
/** Updates {@link DeviceProfile} instances for any Taskbar windows. */
|
||||
public void updateDeviceProfile(DeviceProfile launcherDp, NavigationMode navMode) {
|
||||
mNavMode = navMode;
|
||||
mControllers.taskbarOverlayController.updateLauncherDeviceProfile(launcherDp);
|
||||
matchDeviceProfile(launcherDp, getResources());
|
||||
|
||||
AbstractFloatingView.closeAllOpenViewsExcept(this, false, TYPE_REBIND_SAFE);
|
||||
// Reapply fullscreen to take potential new screen size into account.
|
||||
setTaskbarWindowFullscreen(mIsFullscreen);
|
||||
|
||||
dispatchDeviceProfileChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchDeviceProfileChanged() {
|
||||
super.dispatchDeviceProfileChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the original DeviceProfile, match the number of hotseat icons and qsb width and update
|
||||
* the icon size
|
||||
*/
|
||||
private void matchDeviceProfile(DeviceProfile originDeviceProfile, Resources resources) {
|
||||
mDeviceProfile = originDeviceProfile.toBuilder(this)
|
||||
.withDimensionsOverride(deviceProfile -> {
|
||||
// Taskbar should match the number of icons of hotseat
|
||||
deviceProfile.numShownHotseatIcons = originDeviceProfile.numShownHotseatIcons;
|
||||
// Same QSB width to have a smooth animation
|
||||
deviceProfile.hotseatQsbWidth = originDeviceProfile.hotseatQsbWidth;
|
||||
|
||||
// Update icon size
|
||||
deviceProfile.iconSizePx = deviceProfile.taskbarIconSize;
|
||||
deviceProfile.updateIconSize(1f, resources);
|
||||
}).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the View bounds of transient taskbar.
|
||||
*/
|
||||
@@ -363,11 +377,6 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
* @param title The window title to pass to the created WindowManager.LayoutParams.
|
||||
*/
|
||||
public WindowManager.LayoutParams createDefaultWindowLayoutParams(int type, String title) {
|
||||
DeviceProfile deviceProfile = getDeviceProfile();
|
||||
// Taskbar is on the logical bottom of the screen
|
||||
boolean isVerticalBarLayout = TaskbarManager.isPhoneButtonNavMode(this) &&
|
||||
deviceProfile.isLandscape;
|
||||
|
||||
int windowFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||
| WindowManager.LayoutParams.FLAG_SLIPPERY
|
||||
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
|
||||
@@ -376,17 +385,14 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
|
||||
}
|
||||
WindowManager.LayoutParams windowLayoutParams = new WindowManager.LayoutParams(
|
||||
isVerticalBarLayout ? mLastRequestedNonFullscreenHeight : MATCH_PARENT,
|
||||
isVerticalBarLayout ? MATCH_PARENT : mLastRequestedNonFullscreenHeight,
|
||||
MATCH_PARENT,
|
||||
mLastRequestedNonFullscreenHeight,
|
||||
type,
|
||||
windowFlags,
|
||||
PixelFormat.TRANSLUCENT);
|
||||
windowLayoutParams.setTitle(title);
|
||||
windowLayoutParams.packageName = getPackageName();
|
||||
windowLayoutParams.gravity = !isVerticalBarLayout ?
|
||||
Gravity.BOTTOM :
|
||||
Gravity.END; // TODO(b/230394142): seascape
|
||||
|
||||
windowLayoutParams.gravity = Gravity.BOTTOM;
|
||||
windowLayoutParams.setFitInsetsTypes(0);
|
||||
windowLayoutParams.receiveInsetsIgnoringZOrder = true;
|
||||
windowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
|
||||
@@ -397,6 +403,64 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
TaskbarManager.isPhoneMode(mDeviceProfile)
|
||||
? R.string.taskbar_phone_a11y_title
|
||||
: R.string.taskbar_a11y_title);
|
||||
|
||||
return windowLayoutParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link WindowManager.LayoutParams} for Taskbar, and also sets LP.paramsForRotation
|
||||
* for taskbar showing as navigation bar
|
||||
*/
|
||||
private WindowManager.LayoutParams createAllWindowParams() {
|
||||
WindowManager.LayoutParams windowLayoutParams =
|
||||
createDefaultWindowLayoutParams(TYPE_NAVIGATION_BAR_PANEL,
|
||||
TaskbarActivityContext.WINDOW_TITLE);
|
||||
boolean isPhoneNavMode = TaskbarManager.isPhoneButtonNavMode(this);
|
||||
if (!isPhoneNavMode) {
|
||||
return windowLayoutParams;
|
||||
}
|
||||
|
||||
// Provide WM layout params for all rotations to cache, see NavigationBar#getBarLayoutParams
|
||||
int width = WindowManager.LayoutParams.MATCH_PARENT;
|
||||
int height = WindowManager.LayoutParams.MATCH_PARENT;
|
||||
int gravity = Gravity.BOTTOM;
|
||||
windowLayoutParams.paramsForRotation = new WindowManager.LayoutParams[4];
|
||||
for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
|
||||
WindowManager.LayoutParams lp =
|
||||
createDefaultWindowLayoutParams(TYPE_NAVIGATION_BAR_PANEL,
|
||||
TaskbarActivityContext.WINDOW_TITLE);
|
||||
switch (rot) {
|
||||
case Surface.ROTATION_0, Surface.ROTATION_180 -> {
|
||||
// Defaults are fine
|
||||
width = WindowManager.LayoutParams.MATCH_PARENT;
|
||||
height = mLastRequestedNonFullscreenHeight;
|
||||
gravity = Gravity.BOTTOM;
|
||||
}
|
||||
case Surface.ROTATION_90 -> {
|
||||
width = mLastRequestedNonFullscreenHeight;
|
||||
height = WindowManager.LayoutParams.MATCH_PARENT;
|
||||
gravity = Gravity.END;
|
||||
}
|
||||
case Surface.ROTATION_270 -> {
|
||||
width = mLastRequestedNonFullscreenHeight;
|
||||
height = WindowManager.LayoutParams.MATCH_PARENT;
|
||||
gravity = Gravity.START;
|
||||
}
|
||||
|
||||
}
|
||||
lp.width = width;
|
||||
lp.height = height;
|
||||
lp.gravity = gravity;
|
||||
windowLayoutParams.paramsForRotation[rot] = lp;
|
||||
}
|
||||
|
||||
// Override current layout params
|
||||
WindowManager.LayoutParams currentParams =
|
||||
windowLayoutParams.paramsForRotation[getDisplay().getRotation()];
|
||||
windowLayoutParams.width = currentParams.width;
|
||||
windowLayoutParams.height = currentParams.height;
|
||||
windowLayoutParams.gravity = currentParams.gravity;
|
||||
|
||||
return windowLayoutParams;
|
||||
}
|
||||
|
||||
@@ -446,6 +510,11 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
return mControllers.taskbarDragController;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BubbleControllers getBubbleControllers() {
|
||||
return mControllers.bubbleControllers.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewCache getViewCache() {
|
||||
return mViewCache;
|
||||
@@ -577,6 +646,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
Executors.MAIN_EXECUTOR.getHandler(), null,
|
||||
elapsedRealTime -> callbacks.executeAllAndDestroy());
|
||||
options.setSplashScreenStyle(splashScreenStyle);
|
||||
Utilities.allowBGLaunch(options);
|
||||
return new ActivityOptionsWrapper(options, callbacks);
|
||||
}
|
||||
|
||||
@@ -636,8 +706,12 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
mControllers.taskbarForceVisibleImmersiveController.updateSysuiFlags(systemUiStateFlags);
|
||||
mControllers.voiceInteractionWindowController.setIsVoiceInteractionWindowVisible(
|
||||
(systemUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0, fromInit);
|
||||
|
||||
mControllers.uiController.updateStateForSysuiFlags(systemUiStateFlags);
|
||||
mControllers.bubbleControllers.ifPresent(controllers -> {
|
||||
controllers.bubbleBarController.updateStateForSysuiFlags(systemUiStateFlags);
|
||||
controllers.bubbleStashedHandleViewController.setIsHomeButtonDisabled(
|
||||
mControllers.navbarButtonsViewController.isHomeDisabled());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -780,6 +854,16 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies forcibly show flag to taskbar window iff transient taskbar is unstashed.
|
||||
*/
|
||||
public void applyForciblyShownFlagWhileTransientTaskbarUnstashed(boolean shouldForceShow) {
|
||||
if (!DisplayController.isTransientTaskbar(this)) {
|
||||
return;
|
||||
}
|
||||
notifyUpdateLayoutParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* Either adds or removes {@link WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} on the taskbar
|
||||
* window. If we're now focusable, also move nav buttons to a separate window above IME.
|
||||
@@ -886,8 +970,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
}
|
||||
|
||||
} catch (NullPointerException
|
||||
| ActivityNotFoundException
|
||||
| SecurityException e) {
|
||||
| ActivityNotFoundException
|
||||
| SecurityException e) {
|
||||
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
Log.e(TAG, "Unable to launch. tag=" + info + " intent=" + intent, e);
|
||||
@@ -934,9 +1018,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
if (recents == null) {
|
||||
return;
|
||||
}
|
||||
recents.getSplitSelectController().findLastActiveTaskAndRunCallback(
|
||||
info.getComponentKey(),
|
||||
foundTask -> {
|
||||
recents.getSplitSelectController().findLastActiveTasksAndRunCallback(
|
||||
Collections.singletonList(info.getComponentKey()),
|
||||
foundTasks -> {
|
||||
@Nullable Task foundTask = foundTasks.get(0);
|
||||
if (foundTask != null) {
|
||||
TaskView foundTaskView =
|
||||
recents.getTaskViewByTaskId(foundTask.key.id);
|
||||
@@ -980,6 +1065,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
@@ -990,10 +1076,19 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
* Called when we want to unstash taskbar when user performs swipes up gesture.
|
||||
*/
|
||||
public void onSwipeToUnstashTaskbar() {
|
||||
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(false);
|
||||
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(/* stash= */ false);
|
||||
mControllers.taskbarEduTooltipController.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we want to open bubblebar when user performs swipes up gesture.
|
||||
*/
|
||||
public void onSwipeToOpenBubblebar() {
|
||||
mControllers.bubbleControllers.ifPresent(controllers -> {
|
||||
controllers.bubbleStashController.showBubbleBar(/* expandBubbles= */ true);
|
||||
});
|
||||
}
|
||||
|
||||
/** Returns {@code true} if Taskbar All Apps is open. */
|
||||
public boolean isTaskbarAllAppsOpen() {
|
||||
return mControllers.taskbarAllAppsController.isOpen();
|
||||
@@ -1039,7 +1134,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
* 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.
|
||||
* @param forceUnstash Whether we force the unstash hint.
|
||||
* @param forceUnstash Whether we force the unstash hint.
|
||||
*/
|
||||
public void startTaskbarUnstashHint(boolean animateForward, boolean forceUnstash) {
|
||||
// TODO(b/270395798): Clean up forceUnstash after removing long-press unstashing code.
|
||||
|
||||
@@ -27,8 +27,10 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
/**
|
||||
* 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).
|
||||
* 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 implements
|
||||
TaskbarControllers.LoggableTaskbarController {
|
||||
@@ -45,6 +47,8 @@ public class TaskbarAutohideSuspendController implements
|
||||
public static final int FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER = 1 << 4;
|
||||
// Transient Taskbar is temporarily unstashed (pending a timeout).
|
||||
public static final int FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR = 1 << 5;
|
||||
// User has hovered the taskbar.
|
||||
public static final int FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS = 1 << 6;
|
||||
|
||||
@IntDef(flag = true, value = {
|
||||
FLAG_AUTOHIDE_SUSPEND_FULLSCREEN,
|
||||
@@ -53,9 +57,11 @@ public class TaskbarAutohideSuspendController implements
|
||||
FLAG_AUTOHIDE_SUSPEND_EDU_OPEN,
|
||||
FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER,
|
||||
FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR,
|
||||
FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface AutohideSuspendFlag {}
|
||||
public @interface AutohideSuspendFlag {
|
||||
}
|
||||
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final SystemUiProxy mSystemUiProxy;
|
||||
@@ -72,7 +78,8 @@ public class TaskbarAutohideSuspendController implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or removes the given flag, then notifies system UI proxy whether to suspend auto-hide.
|
||||
* Adds or removes the given flag, then notifies system UI proxy whether to
|
||||
* suspend auto-hide.
|
||||
*/
|
||||
public void updateFlag(@AutohideSuspendFlag int flag, boolean enabled) {
|
||||
int flagsBefore = mAutohideSuspendFlags;
|
||||
@@ -99,7 +106,8 @@ public class TaskbarAutohideSuspendController implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether Transient Taskbar should avoid auto-stashing in Launcher(Overview).
|
||||
* Returns whether Transient Taskbar should avoid auto-stashing in
|
||||
* Launcher(Overview).
|
||||
*/
|
||||
public boolean isSuspendedForTransientTaskbarInLauncher() {
|
||||
return (mAutohideSuspendFlags & FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER) != 0;
|
||||
|
||||
@@ -22,12 +22,12 @@ import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import android.graphics.RectF
|
||||
import com.android.app.animation.Interpolators
|
||||
import com.android.launcher3.DeviceProfile
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.Utilities
|
||||
import com.android.launcher3.Utilities.mapRange
|
||||
import com.android.launcher3.Utilities.mapToRange
|
||||
import com.android.launcher3.anim.Interpolators
|
||||
import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
|
||||
import com.android.launcher3.util.DisplayController
|
||||
|
||||
|
||||
@@ -239,6 +239,7 @@ public class TaskbarControllers {
|
||||
taskbarPopupController.onDestroy();
|
||||
taskbarForceVisibleImmersiveController.onDestroy();
|
||||
taskbarOverlayController.onDestroy();
|
||||
taskbarAllAppsController.onDestroy();
|
||||
navButtonController.onDestroy();
|
||||
taskbarInsetsController.onDestroy();
|
||||
voiceInteractionWindowController.onDestroy();
|
||||
|
||||
@@ -17,7 +17,6 @@ package com.android.launcher3.taskbar
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.util.AttributeSet
|
||||
@@ -44,9 +43,6 @@ constructor(
|
||||
companion object {
|
||||
private const val TAG = "TaskbarDividerPopupView"
|
||||
private const val DIVIDER_POPUP_CLOSING_DELAY = 500L
|
||||
private const val SETTINGS_PACKAGE_NAME = "com.android.settings"
|
||||
private const val CHANGE_NAVIGATION_MODE_ACTION =
|
||||
"com.android.settings.NAVIGATION_MODE_SETTINGS"
|
||||
|
||||
@JvmStatic
|
||||
fun createAndPopulate(
|
||||
@@ -103,21 +99,12 @@ constructor(
|
||||
super.onFinishInflate()
|
||||
val taskbarSwitchOption = findViewById<LinearLayout>(R.id.taskbar_switch_option)
|
||||
val alwaysShowTaskbarSwitch = findViewById<Switch>(R.id.taskbar_pinning_switch)
|
||||
val navigationModeChangeOption =
|
||||
findViewById<LinearLayout>(R.id.navigation_mode_switch_option)
|
||||
alwaysShowTaskbarSwitch.isChecked = alwaysShowTaskbarOn
|
||||
taskbarSwitchOption.setOnClickListener {
|
||||
alwaysShowTaskbarSwitch.isClickable = true
|
||||
alwaysShowTaskbarSwitch.isChecked = !alwaysShowTaskbarOn
|
||||
onClickAlwaysShowTaskbarSwitchOption()
|
||||
}
|
||||
navigationModeChangeOption.setOnClickListener {
|
||||
context.startActivity(
|
||||
Intent(CHANGE_NAVIGATION_MODE_ACTION)
|
||||
.setPackage(SETTINGS_PACKAGE_NAME)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Orient object as usual and then center object horizontally. */
|
||||
|
||||
@@ -15,16 +15,18 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
|
||||
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.NonNull;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipDescription;
|
||||
import android.content.Intent;
|
||||
@@ -47,6 +49,7 @@ import android.window.SurfaceSyncGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.internal.logging.InstanceId;
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
@@ -55,7 +58,6 @@ 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.anim.Interpolators;
|
||||
import com.android.launcher3.dragndrop.DragController;
|
||||
import com.android.launcher3.dragndrop.DragDriver;
|
||||
import com.android.launcher3.dragndrop.DragOptions;
|
||||
@@ -73,6 +75,7 @@ import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.IntSet;
|
||||
import com.android.launcher3.util.ItemInfoMatcher;
|
||||
import com.android.launcher3.views.BubbleTextHolder;
|
||||
import com.android.quickstep.util.LogUtils;
|
||||
import com.android.quickstep.util.MultiValueUpdateListener;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
@@ -84,7 +87,8 @@ import java.util.Collections;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Handles long click on Taskbar items to start a system drag and drop operation.
|
||||
* Handles long click on Taskbar items to start a system drag and drop
|
||||
* operation.
|
||||
*/
|
||||
public class TaskbarDragController extends DragController<BaseTaskbarContext> implements
|
||||
TaskbarControllers.LoggableTaskbarController {
|
||||
@@ -129,8 +133,10 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to start a system drag and drop operation for the given View, using its tag to
|
||||
* 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.
|
||||
*/
|
||||
public boolean startDragOnLongClick(View view) {
|
||||
@@ -149,6 +155,9 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
View view,
|
||||
@Nullable DragPreviewProvider dragPreviewProvider,
|
||||
@Nullable Point iconShift) {
|
||||
if (view instanceof BubbleTextHolder) {
|
||||
view = ((BubbleTextHolder) view).getBubbleText();
|
||||
}
|
||||
if (!(view instanceof BubbleTextView) || mDisallowLongClick) {
|
||||
return false;
|
||||
}
|
||||
@@ -182,7 +191,8 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
btv.clearPressedBackground();
|
||||
|
||||
final DragPreviewProvider previewProvider = dragPreviewProvider == null
|
||||
? new DragPreviewProvider(btv) : dragPreviewProvider;
|
||||
? new DragPreviewProvider(btv)
|
||||
: dragPreviewProvider;
|
||||
final Drawable drawable = previewProvider.createDrawable();
|
||||
final float scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
|
||||
int dragLayerX = mTempXY[0];
|
||||
@@ -193,13 +203,20 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
dragLayerY += dragRect.top;
|
||||
|
||||
DragOptions dragOptions = new DragOptions();
|
||||
dragOptions.preDragCondition = null;
|
||||
PopupContainerWithArrow<BaseTaskbarContext> popupContainer =
|
||||
mControllers.taskbarPopupController.showForIcon(btv);
|
||||
if (popupContainer != null) {
|
||||
dragOptions.preDragCondition = popupContainer.createPreDragCondition(false);
|
||||
}
|
||||
// First, see if view is a search result that needs custom pre-drag conditions.
|
||||
dragOptions.preDragCondition = mControllers.taskbarAllAppsController.createPreDragConditionForSearch(btv);
|
||||
|
||||
if (dragOptions.preDragCondition == null) {
|
||||
// See if view supports a popup container.
|
||||
PopupContainerWithArrow<BaseTaskbarContext> popupContainer = mControllers.taskbarPopupController
|
||||
.showForIcon(btv);
|
||||
if (popupContainer != null) {
|
||||
dragOptions.preDragCondition = popupContainer.createPreDragCondition(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (dragOptions.preDragCondition == null) {
|
||||
// Fallback pre-drag condition.
|
||||
dragOptions.preDragCondition = new DragOptions.PreDragCondition() {
|
||||
private DragView mDragView;
|
||||
|
||||
@@ -213,13 +230,8 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
mDragView = dragObject.dragView;
|
||||
|
||||
if (!shouldStartDrag(0)) {
|
||||
mDragView.setOnAnimationEndCallback(() -> {
|
||||
// Drag might be cancelled during the DragView animation, so check
|
||||
// mIsPreDrag again.
|
||||
if (mIsInPreDrag) {
|
||||
callOnDragStart();
|
||||
}
|
||||
});
|
||||
mDragView.setOnScaleAnimEndCallback(
|
||||
TaskbarDragController.this::onPreDragAnimationEnd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,13 +242,15 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
};
|
||||
}
|
||||
|
||||
Point dragOffset = dragOptions.preDragCondition.getDragOffset();
|
||||
return startDrag(
|
||||
drawable,
|
||||
/* view = */ null,
|
||||
/* originalView = */ btv,
|
||||
dragLayerX,
|
||||
dragLayerY,
|
||||
(View target, DropTarget.DragObject d, boolean success) -> {} /* DragSource */,
|
||||
dragLayerX + dragOffset.x,
|
||||
dragLayerY + dragOffset.y,
|
||||
(View target, DropTarget.DragObject d, boolean success) -> {
|
||||
} /* DragSource */,
|
||||
(ItemInfo) btv.getTag(),
|
||||
dragRect,
|
||||
scale * iconScale,
|
||||
@@ -281,7 +295,8 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft);
|
||||
mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop);
|
||||
|
||||
mDragDriver = DragDriver.create(this, mOptions, /* secondaryEventConsumer = */ ev -> {});
|
||||
mDragDriver = DragDriver.create(this, mOptions, /* secondaryEventConsumer = */ ev -> {
|
||||
});
|
||||
if (!mOptions.isAccessibleDrag) {
|
||||
mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
|
||||
}
|
||||
@@ -290,6 +305,11 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
mDragObject.dragInfo = dragInfo;
|
||||
mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy();
|
||||
|
||||
if (mOptions.preDragCondition != null) {
|
||||
dragView.setHasDragOffset(mOptions.preDragCondition.getDragOffset().x != 0
|
||||
|| mOptions.preDragCondition.getDragOffset().y != 0);
|
||||
}
|
||||
|
||||
if (dragRegion != null) {
|
||||
dragView.setDragRegion(new Rect(dragRegion));
|
||||
}
|
||||
@@ -308,6 +328,15 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
return dragView;
|
||||
}
|
||||
|
||||
/** Invoked when an animation running as part of pre-drag finishes. */
|
||||
public void onPreDragAnimationEnd() {
|
||||
// Drag might be cancelled during the DragView animation, so check mIsPreDrag
|
||||
// again.
|
||||
if (mIsInPreDrag) {
|
||||
callOnDragStart();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void callOnDragStart() {
|
||||
super.callOnDragStart();
|
||||
@@ -383,6 +412,18 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
item.user));
|
||||
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, item.getIntent().getPackage());
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_ID, deepShortcutId);
|
||||
} else if (item.itemType == ITEM_TYPE_SEARCH_ACTION) {
|
||||
// TODO(b/289261756): Buggy behavior when split opposite to an existing search
|
||||
// pane.
|
||||
intent.putExtra(
|
||||
ClipDescription.EXTRA_PENDING_INTENT,
|
||||
PendingIntent.getActivityAsUser(
|
||||
mActivity,
|
||||
/* requestCode= */ 0,
|
||||
item.getIntent(),
|
||||
PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
|
||||
/* options= */ null,
|
||||
item.user));
|
||||
} else {
|
||||
intent.putExtra(ClipDescription.EXTRA_PENDING_INTENT,
|
||||
launcherApps.getMainActivityLaunchIntent(item.getIntent().getComponent(),
|
||||
@@ -401,8 +442,8 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
}
|
||||
|
||||
if (clipDescription != null && intent != null) {
|
||||
Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
|
||||
LogUtils.getShellShareableInstanceId();
|
||||
Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds = LogUtils
|
||||
.getShellShareableInstanceId();
|
||||
// Need to share the same InstanceId between launcher3 and WM Shell (internal).
|
||||
InstanceId internalInstanceId = instanceIds.first;
|
||||
com.android.launcher3.logging.InstanceId launcherInstanceId = instanceIds.second;
|
||||
@@ -485,13 +526,18 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
@Override
|
||||
protected void endDrag() {
|
||||
if (mDisallowGlobalDrag) {
|
||||
// We need to explicitly set deferDragViewCleanupPostAnimation to true here so the
|
||||
// super call doesn't remove it from the drag layer before the animation completes.
|
||||
// We need to explicitly set deferDragViewCleanupPostAnimation to true here so
|
||||
// the
|
||||
// super call doesn't remove it from the drag layer before the animation
|
||||
// completes.
|
||||
// This variable gets set in to false in super.dispatchDropComplete() because it
|
||||
// (rightfully so, perhaps) thinks this drag operation has failed, and does its own
|
||||
// (rightfully so, perhaps) thinks this drag operation has failed, and does its
|
||||
// own
|
||||
// internal cleanup.
|
||||
// Another way to approach this would be to make all of overview a drop target and
|
||||
// accept the drop as successful and then run the setupReturnDragAnimator to simulate
|
||||
// Another way to approach this would be to make all of overview a drop target
|
||||
// and
|
||||
// accept the drop as successful and then run the setupReturnDragAnimator to
|
||||
// simulate
|
||||
// drop failure to the user
|
||||
mDragObject.deferDragViewCleanupPostAnimation = true;
|
||||
|
||||
@@ -576,7 +622,8 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
syncGroup.add(viewRoot, null /* runnable */);
|
||||
syncGroup.addTransaction(transaction);
|
||||
syncGroup.markSyncReady();
|
||||
// Do this after maybeOnDragEnd(), because we use mReturnAnimator != null to imply
|
||||
// Do this after maybeOnDragEnd(), because we use mReturnAnimator != null to
|
||||
// imply
|
||||
// the drag was canceled rather than successful.
|
||||
mReturnAnimator = null;
|
||||
}
|
||||
@@ -595,7 +642,8 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
// We're dragging in taskbarAllApps, we don't have folders or shortcuts
|
||||
return iconView;
|
||||
}
|
||||
// Since all apps closes when the drag starts, target the all apps button instead.
|
||||
// Since all apps closes when the drag starts, target the all apps button
|
||||
// instead.
|
||||
return taskbarViewController.getAllAppsButtonView();
|
||||
} else if (item.container >= 0) {
|
||||
// Since folders close when the drag starts, target the folder icon instead.
|
||||
@@ -642,7 +690,8 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
final FloatProp mScale = new FloatProp(1f, toScale, 0,
|
||||
ANIM_DURATION_RETURN_ICON_TO_TASKBAR, FAST_OUT_SLOW_IN);
|
||||
final FloatProp mAlpha = new FloatProp(1f, toAlpha, 0,
|
||||
ANIM_DURATION_RETURN_ICON_TO_TASKBAR, Interpolators.ACCEL_2);
|
||||
ANIM_DURATION_RETURN_ICON_TO_TASKBAR, Interpolators.ACCELERATE_2);
|
||||
|
||||
@Override
|
||||
public void onUpdate(float percent, boolean initOnly) {
|
||||
animListener.updateDragShadow(mDx.value, mDy.value, mScale.value, mAlpha.value);
|
||||
@@ -656,15 +705,19 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
|
||||
@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.
|
||||
// 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.
|
||||
// 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import static android.view.KeyEvent.KEYCODE_BACK;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.RectF;
|
||||
import android.media.permission.SafeCloseable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.FloatProperty;
|
||||
import android.view.KeyEvent;
|
||||
@@ -31,6 +32,7 @@ import android.view.ViewTreeObserver;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.app.viewcapture.SettingsAwareViewCapture;
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
@@ -68,6 +70,7 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
|
||||
// Initialized in init.
|
||||
private TaskbarDragLayerController.TaskbarDragLayerCallbacks mControllerCallbacks;
|
||||
private SafeCloseable mViewCaptureCloseable;
|
||||
|
||||
private float mTaskbarBackgroundOffset;
|
||||
|
||||
@@ -128,12 +131,14 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
getViewTreeObserver().addOnComputeInternalInsetsListener(mTaskbarInsetsComputer);
|
||||
mViewCaptureCloseable = SettingsAwareViewCapture.getInstance(getContext())
|
||||
.startCapture(getRootView(), ".Taskbar");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
mViewCaptureCloseable.close();
|
||||
onDestroy(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.MotionEvent.ACTION_HOVER_ENTER;
|
||||
import static android.view.MotionEvent.ACTION_HOVER_EXIT;
|
||||
import static android.view.View.ALPHA;
|
||||
import static android.view.View.SCALE_Y;
|
||||
import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT;
|
||||
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_EXCEPT_ON_BOARD_POPUP;
|
||||
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS;
|
||||
import static com.android.launcher3.views.ArrowTipView.TEXT_ALPHA;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.compat.AccessibilityManagerCompat;
|
||||
import com.android.launcher3.folder.FolderIcon;
|
||||
import com.android.launcher3.views.ArrowTipView;
|
||||
|
||||
/**
|
||||
* Controls showing a tooltip in the taskbar above each icon when it is hovered.
|
||||
*/
|
||||
public class TaskbarHoverToolTipController implements View.OnHoverListener {
|
||||
|
||||
@VisibleForTesting protected static final int HOVER_TOOL_TIP_REVEAL_START_DELAY = 400;
|
||||
private static final int HOVER_TOOL_TIP_REVEAL_DURATION = 300;
|
||||
private static final int HOVER_TOOL_TIP_EXIT_DURATION = 150;
|
||||
|
||||
private final Handler mHoverToolTipHandler = new Handler(Looper.getMainLooper());
|
||||
private final Runnable mRevealHoverToolTipRunnable = this::revealHoverToolTip;
|
||||
private final Runnable mHideHoverToolTipRunnable = this::hideHoverToolTip;
|
||||
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final TaskbarView mTaskbarView;
|
||||
private final View mHoverView;
|
||||
private final ArrowTipView mHoverToolTipView;
|
||||
private final String mToolTipText;
|
||||
|
||||
public TaskbarHoverToolTipController(TaskbarActivityContext activity, TaskbarView taskbarView,
|
||||
View hoverView) {
|
||||
mActivity = activity;
|
||||
mTaskbarView = taskbarView;
|
||||
mHoverView = hoverView;
|
||||
|
||||
if (mHoverView instanceof BubbleTextView) {
|
||||
mToolTipText = ((BubbleTextView) mHoverView).getText().toString();
|
||||
} else if (mHoverView instanceof FolderIcon
|
||||
&& ((FolderIcon) mHoverView).mInfo.title != null) {
|
||||
mToolTipText = ((FolderIcon) mHoverView).mInfo.title.toString();
|
||||
} else {
|
||||
mToolTipText = null;
|
||||
}
|
||||
|
||||
ContextThemeWrapper arrowContextWrapper = new ContextThemeWrapper(mActivity,
|
||||
R.style.ArrowTipTaskbarStyle);
|
||||
mHoverToolTipView = new ArrowTipView(arrowContextWrapper, /* isPointingUp = */ false,
|
||||
R.layout.arrow_toast);
|
||||
|
||||
AnimatorSet hoverCloseAnimator = new AnimatorSet();
|
||||
ObjectAnimator textCloseAnimator = ObjectAnimator.ofInt(mHoverToolTipView, TEXT_ALPHA, 0);
|
||||
textCloseAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0, 0.33f));
|
||||
ObjectAnimator alphaCloseAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, ALPHA, 0);
|
||||
alphaCloseAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.33f, 0.66f));
|
||||
ObjectAnimator scaleCloseAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, SCALE_Y, 0);
|
||||
scaleCloseAnimator.setInterpolator(Interpolators.STANDARD);
|
||||
hoverCloseAnimator.playTogether(
|
||||
textCloseAnimator,
|
||||
alphaCloseAnimator,
|
||||
scaleCloseAnimator);
|
||||
hoverCloseAnimator.setStartDelay(0);
|
||||
hoverCloseAnimator.setDuration(HOVER_TOOL_TIP_EXIT_DURATION);
|
||||
mHoverToolTipView.setCustomCloseAnimation(hoverCloseAnimator);
|
||||
|
||||
AnimatorSet hoverOpenAnimator = new AnimatorSet();
|
||||
ObjectAnimator textOpenAnimator = ObjectAnimator.ofInt(mHoverToolTipView, TEXT_ALPHA, 255);
|
||||
textOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.33f, 1f));
|
||||
ObjectAnimator scaleOpenAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, SCALE_Y, 1f);
|
||||
scaleOpenAnimator.setInterpolator(Interpolators.EMPHASIZED);
|
||||
ObjectAnimator alphaOpenAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, ALPHA, 1f);
|
||||
alphaOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.1f, 0.33f));
|
||||
hoverOpenAnimator.playTogether(
|
||||
scaleOpenAnimator,
|
||||
textOpenAnimator,
|
||||
alphaOpenAnimator);
|
||||
hoverOpenAnimator.setStartDelay(HOVER_TOOL_TIP_REVEAL_START_DELAY);
|
||||
hoverOpenAnimator.setDuration(HOVER_TOOL_TIP_REVEAL_DURATION);
|
||||
mHoverToolTipView.setCustomOpenAnimation(hoverOpenAnimator);
|
||||
|
||||
mHoverToolTipView.addOnLayoutChangeListener(
|
||||
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
||||
mHoverToolTipView.setPivotY(bottom);
|
||||
mHoverToolTipView.setY(mTaskbarView.getTop() - (bottom - top));
|
||||
});
|
||||
mHoverToolTipView.setScaleY(0f);
|
||||
mHoverToolTipView.setAlpha(0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onHover(View v, MotionEvent event) {
|
||||
boolean isAnyOtherFloatingViewOpen =
|
||||
AbstractFloatingView.hasOpenView(mActivity, TYPE_ALL_EXCEPT_ON_BOARD_POPUP);
|
||||
if (isAnyOtherFloatingViewOpen) {
|
||||
mHoverToolTipHandler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
// If hover leaves a taskbar icon animate the tooltip closed.
|
||||
if (event.getAction() == ACTION_HOVER_EXIT) {
|
||||
startHideHoverToolTip();
|
||||
mActivity.setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, false);
|
||||
return true;
|
||||
} else if (!isAnyOtherFloatingViewOpen && event.getAction() == ACTION_HOVER_ENTER) {
|
||||
// If hovering above a taskbar icon starts, animate the tooltip open. Do not
|
||||
// reveal if any floating views such as folders or edu pop-ups are open.
|
||||
startRevealHoverToolTip();
|
||||
mActivity.setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void startRevealHoverToolTip() {
|
||||
mHoverToolTipHandler.postDelayed(mRevealHoverToolTipRunnable,
|
||||
HOVER_TOOL_TIP_REVEAL_START_DELAY);
|
||||
}
|
||||
|
||||
private void revealHoverToolTip() {
|
||||
if (mHoverView == null || mToolTipText == null) {
|
||||
return;
|
||||
}
|
||||
if (mHoverView instanceof FolderIcon && !((FolderIcon) mHoverView).getIconVisible()) {
|
||||
return;
|
||||
}
|
||||
mActivity.setTaskbarWindowFullscreen(true);
|
||||
Rect iconViewBounds = Utilities.getViewBounds(mHoverView);
|
||||
mHoverToolTipView.showAtLocation(mToolTipText, iconViewBounds.centerX(),
|
||||
mTaskbarView.getTop(), /* shouldAutoClose= */ false);
|
||||
}
|
||||
|
||||
private void startHideHoverToolTip() {
|
||||
mHoverToolTipHandler.removeCallbacks(mRevealHoverToolTipRunnable);
|
||||
int accessibilityHideTimeout = AccessibilityManagerCompat.getRecommendedTimeoutMillis(
|
||||
mActivity, /* originalTimeout= */ 0, FLAG_CONTENT_TEXT);
|
||||
mHoverToolTipHandler.postDelayed(mHideHoverToolTipRunnable, accessibilityHideTimeout);
|
||||
}
|
||||
|
||||
private void hideHoverToolTip() {
|
||||
mHoverToolTipView.close(/* animate = */ true);
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import android.view.InsetsSource.FLAG_SUPPRESS_SCRIM
|
||||
import android.view.ViewTreeObserver
|
||||
import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME
|
||||
import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowInsets.Type.mandatorySystemGestures
|
||||
import android.view.WindowInsets.Type.navigationBars
|
||||
import android.view.WindowInsets.Type.systemGestures
|
||||
@@ -46,12 +47,10 @@ import kotlin.jvm.optionals.getOrNull
|
||||
/** Handles the insets that Taskbar provides to underlying apps and the IME. */
|
||||
class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTaskbarController {
|
||||
|
||||
private val FLAG_INSETS_ROUNDED_CORNER = 1 shl 1
|
||||
private val ENABLE_HIDE_IME_CAPTION_BAR = true
|
||||
|
||||
companion object {
|
||||
private const val INDEX_LEFT = 0
|
||||
private const val INDEX_RIGHT = 1
|
||||
const val FLAG_INSETS_ROUNDED_CORNER = 1 shl 1
|
||||
}
|
||||
|
||||
/** The bottom insets taskbar provides to the IME when IME is visible. */
|
||||
@@ -117,7 +116,7 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
|
||||
if (
|
||||
controllers.bubbleControllers.isPresent &&
|
||||
controllers.bubbleControllers.get().bubbleStashController.isBubblesShowingOnHome
|
||||
controllers.bubbleControllers.get().bubbleStashController.isBubblesShowingOnHome
|
||||
) {
|
||||
val iconBounds =
|
||||
controllers.bubbleControllers.get().bubbleBarViewController.bubbleBarBounds
|
||||
@@ -161,19 +160,19 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
*/
|
||||
private fun getProvidedInsets(insetsRoundedCornerFlag: Int): Array<InsetsFrameProvider> {
|
||||
val navBarsFlag =
|
||||
(if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
|
||||
(if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
|
||||
return arrayOf(
|
||||
InsetsFrameProvider(insetsOwner, 0, navigationBars())
|
||||
.setFlags(
|
||||
navBarsFlag,
|
||||
FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER
|
||||
),
|
||||
InsetsFrameProvider(insetsOwner, 0, tappableElement()),
|
||||
InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()),
|
||||
InsetsFrameProvider(insetsOwner, INDEX_LEFT, systemGestures())
|
||||
.setSource(SOURCE_DISPLAY),
|
||||
InsetsFrameProvider(insetsOwner, INDEX_RIGHT, systemGestures())
|
||||
.setSource(SOURCE_DISPLAY)
|
||||
InsetsFrameProvider(insetsOwner, 0, navigationBars())
|
||||
.setFlags(
|
||||
navBarsFlag,
|
||||
FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER
|
||||
),
|
||||
InsetsFrameProvider(insetsOwner, 0, tappableElement()),
|
||||
InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()),
|
||||
InsetsFrameProvider(insetsOwner, INDEX_LEFT, systemGestures())
|
||||
.setSource(SOURCE_DISPLAY),
|
||||
InsetsFrameProvider(insetsOwner, INDEX_RIGHT, systemGestures())
|
||||
.setSource(SOURCE_DISPLAY)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -187,42 +186,42 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
provider.insetsSize = getInsetsForGravity(tappableHeight, gravity)
|
||||
} else if (provider.type == systemGestures() && provider.index == INDEX_LEFT) {
|
||||
val leftIndexInset =
|
||||
if (context.isThreeButtonNav) 0
|
||||
else gestureNavSettingsObserver.getLeftSensitivityForCallingUser(res)
|
||||
if (context.isThreeButtonNav) 0
|
||||
else gestureNavSettingsObserver.getLeftSensitivityForCallingUser(res)
|
||||
provider.insetsSize = Insets.of(leftIndexInset, 0, 0, 0)
|
||||
} else if (provider.type == systemGestures() && provider.index == INDEX_RIGHT) {
|
||||
val rightIndexInset =
|
||||
if (context.isThreeButtonNav) 0
|
||||
else gestureNavSettingsObserver.getRightSensitivityForCallingUser(res)
|
||||
if (context.isThreeButtonNav) 0
|
||||
else gestureNavSettingsObserver.getRightSensitivityForCallingUser(res)
|
||||
provider.insetsSize = Insets.of(0, 0, rightIndexInset, 0)
|
||||
}
|
||||
|
||||
|
||||
// When in gesture nav, report the stashed height to the IME, to allow hiding the
|
||||
// IME navigation bar.
|
||||
val imeInsetsSize = if (ENABLE_HIDE_IME_CAPTION_BAR && context.isGestureNav) {
|
||||
val imeInsetsSize = if (context.isGestureNav) {
|
||||
getInsetsForGravity(controllers.taskbarStashController.stashedHeight, gravity);
|
||||
} else {
|
||||
getInsetsForGravity(taskbarHeightForIme, gravity)
|
||||
}
|
||||
val imeInsetsSizeOverride =
|
||||
arrayOf(
|
||||
InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
|
||||
)
|
||||
arrayOf(
|
||||
InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
|
||||
)
|
||||
// Use 0 tappableElement insets for the VoiceInteractionWindow when gesture nav is enabled.
|
||||
val visInsetsSizeForTappableElement =
|
||||
if (context.isGestureNav) getInsetsForGravity(0, gravity)
|
||||
else getInsetsForGravity(tappableHeight, gravity)
|
||||
if (context.isGestureNav) getInsetsForGravity(0, gravity)
|
||||
else getInsetsForGravity(tappableHeight, gravity)
|
||||
val insetsSizeOverrideForTappableElement =
|
||||
arrayOf(
|
||||
InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
|
||||
InsetsFrameProvider.InsetsSizeOverride(
|
||||
TYPE_VOICE_INTERACTION,
|
||||
visInsetsSizeForTappableElement
|
||||
),
|
||||
)
|
||||
arrayOf(
|
||||
InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
|
||||
InsetsFrameProvider.InsetsSizeOverride(
|
||||
TYPE_VOICE_INTERACTION,
|
||||
visInsetsSizeForTappableElement
|
||||
),
|
||||
)
|
||||
if ((context.isGestureNav || TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
|
||||
&& provider.type == tappableElement()) {
|
||||
&& provider.type == tappableElement()) {
|
||||
provider.insetsSizeOverrides = insetsSizeOverrideForTappableElement
|
||||
} else if (provider.type != systemGestures()) {
|
||||
// We only override insets at the bottom of the screen
|
||||
@@ -261,14 +260,14 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
)
|
||||
val bubbleBarVisible =
|
||||
controllers.bubbleControllers.isPresent &&
|
||||
controllers.bubbleControllers.get().bubbleBarViewController.isBubbleBarVisible()
|
||||
controllers.bubbleControllers.get().bubbleBarViewController.isBubbleBarVisible()
|
||||
var insetsIsTouchableRegion = true
|
||||
if (context.dragLayer.alpha < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
|
||||
// Let touches pass through us.
|
||||
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
|
||||
} else if (
|
||||
controllers.navbarButtonsViewController.isImeVisible &&
|
||||
controllers.taskbarStashController.isStashed
|
||||
controllers.taskbarStashController.isStashed
|
||||
) {
|
||||
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
|
||||
} else if (!controllers.uiController.isTaskbarTouchable) {
|
||||
@@ -283,19 +282,19 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
insetsIsTouchableRegion = false
|
||||
} else if (
|
||||
controllers.taskbarViewController.areIconsVisible() ||
|
||||
context.isNavBarKidsModeActive ||
|
||||
bubbleBarVisible
|
||||
context.isNavBarKidsModeActive ||
|
||||
bubbleBarVisible
|
||||
) {
|
||||
// Taskbar has some touchable elements, take over the full taskbar area
|
||||
if (
|
||||
controllers.uiController.isInOverview &&
|
||||
DisplayController.isTransientTaskbar(context)
|
||||
DisplayController.isTransientTaskbar(context)
|
||||
) {
|
||||
val region =
|
||||
controllers.taskbarActivityContext.dragLayer.lastDrawnTransientRect.toRegion()
|
||||
val bubbleBarBounds =
|
||||
controllers.bubbleControllers.getOrNull()?.let { bubbleControllers ->
|
||||
if (!bubbleControllers.bubbleStashController.isBubblesShowingOnHome) {
|
||||
if (!bubbleControllers.bubbleStashController.isBubblesShowingOnOverview) {
|
||||
return@let null
|
||||
}
|
||||
if (!bubbleControllers.bubbleBarViewController.isBubbleBarVisible) {
|
||||
@@ -323,22 +322,22 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
override fun dumpLogs(prefix: String, pw: PrintWriter) {
|
||||
pw.println(prefix + "TaskbarInsetsController:")
|
||||
pw.println("$prefix\twindowHeight=${windowLayoutParams.height}")
|
||||
// for (provider in windowLayoutParams.providedInsets) {
|
||||
// pw.print(
|
||||
// "$prefix\tprovidedInsets: (type=" +
|
||||
// WindowInsets.Type.toString(provider.type) +
|
||||
// " insetsSize=" +
|
||||
// provider.insetsSize
|
||||
// )
|
||||
// if (provider.insetsSizeOverrides != null) {
|
||||
// pw.print(" insetsSizeOverrides={")
|
||||
// for ((i, overrideSize) in provider.insetsSizeOverrides.withIndex()) {
|
||||
// if (i > 0) pw.print(", ")
|
||||
// pw.print(overrideSize)
|
||||
// }
|
||||
// pw.print("})")
|
||||
// }
|
||||
// pw.println()
|
||||
// }
|
||||
for (provider in windowLayoutParams.providedInsets) {
|
||||
pw.print(
|
||||
"$prefix\tprovidedInsets: (type=" +
|
||||
WindowInsets.Type.toString(provider.type) +
|
||||
" insetsSize=" +
|
||||
provider.insetsSize
|
||||
)
|
||||
if (provider.insetsSizeOverrides != null) {
|
||||
pw.print(" insetsSizeOverrides={")
|
||||
for ((i, overrideSize) in provider.insetsSizeOverrides.withIndex()) {
|
||||
if (i > 0) pw.print(", ")
|
||||
pw.print(overrideSize)
|
||||
}
|
||||
pw.print("})")
|
||||
}
|
||||
pw.println()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,8 @@ import java.util.HashMap;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
/**
|
||||
* Track LauncherState, RecentsAnimation, resumed state for task bar in one place here and animate
|
||||
* Track LauncherState, RecentsAnimation, resumed state for task bar in one
|
||||
* place here and animate
|
||||
* the task bar accordingly.
|
||||
*/
|
||||
public class TaskbarLauncherStateController {
|
||||
@@ -70,12 +71,14 @@ public class TaskbarLauncherStateController {
|
||||
public static final int FLAG_RESUMED = 1 << 0;
|
||||
|
||||
/**
|
||||
* A external transition / animation is running that will result in FLAG_RESUMED being set.
|
||||
* A external transition / animation is running that will result in FLAG_RESUMED
|
||||
* being set.
|
||||
**/
|
||||
public static final int FLAG_TRANSITION_TO_RESUMED = 1 << 1;
|
||||
|
||||
/**
|
||||
* Set while the launcher state machine is performing a state transition, see {@link
|
||||
* Set while the launcher state machine is performing a state transition, see
|
||||
* {@link
|
||||
* StateManager.StateListener}.
|
||||
*/
|
||||
public static final int FLAG_LAUNCHER_IN_STATE_TRANSITION = 1 << 2;
|
||||
@@ -88,11 +91,14 @@ public class TaskbarLauncherStateController {
|
||||
private static final int FLAG_AWAKE = 1 << 3;
|
||||
|
||||
/**
|
||||
* Captures whether the launcher was active at the time the FLAG_AWAKE was cleared.
|
||||
* Captures whether the launcher was active at the time the FLAG_AWAKE was
|
||||
* cleared.
|
||||
* Always cleared when FLAG_AWAKE is set.
|
||||
* <p>
|
||||
* FLAG_RESUMED will be cleared when the device is asleep, since all apps get paused at this
|
||||
* point. Thus, this flag indicates whether the launcher will be shown when the device wakes up
|
||||
* FLAG_RESUMED will be cleared when the device is asleep, since all apps get
|
||||
* paused at this
|
||||
* point. Thus, this flag indicates whether the launcher will be shown when the
|
||||
* device wakes up
|
||||
* again.
|
||||
*/
|
||||
private static final int FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE = 1 << 4;
|
||||
@@ -100,18 +106,23 @@ public class TaskbarLauncherStateController {
|
||||
/**
|
||||
* Whether the device is currently locked.
|
||||
* <ul>
|
||||
* <li>While locked, the taskbar is always stashed.<li/>
|
||||
* <li>Navbar animations on FLAG_DEVICE_LOCKED transitions will get special treatment.</li>
|
||||
* <li>While locked, the taskbar is always stashed.
|
||||
* <li/>
|
||||
* <li>Navbar animations on FLAG_DEVICE_LOCKED transitions will get special
|
||||
* treatment.</li>
|
||||
* </ul>
|
||||
*/
|
||||
private static final int FLAG_DEVICE_LOCKED = 1 << 5;
|
||||
|
||||
/**
|
||||
* Whether the complete taskbar is completely hidden (neither visible stashed or unstashed).
|
||||
* This is tracked to allow a nice transition of the taskbar before SysUI forces it away by
|
||||
* Whether the complete taskbar is completely hidden (neither visible stashed or
|
||||
* unstashed).
|
||||
* This is tracked to allow a nice transition of the taskbar before SysUI forces
|
||||
* it away by
|
||||
* hiding the inset.
|
||||
*
|
||||
* This flag is predominanlty set while FLAG_DEVICE_LOCKED is set, thus the taskbar's invisible
|
||||
* This flag is predominanlty set while FLAG_DEVICE_LOCKED is set, thus the
|
||||
* taskbar's invisible
|
||||
* resting state while hidden is stashed.
|
||||
*/
|
||||
private static final int FLAG_TASKBAR_HIDDEN = 1 << 6;
|
||||
@@ -127,14 +138,15 @@ public class TaskbarLauncherStateController {
|
||||
/**
|
||||
* Delay for the taskbar fade-in.
|
||||
*
|
||||
* Helps to avoid visual noise when unlocking successfully via SFPS, and the device transitions
|
||||
* to launcher directly. The delay avoids the navbar to become briefly visible. The duration
|
||||
* Helps to avoid visual noise when unlocking successfully via SFPS, and the
|
||||
* device transitions
|
||||
* to launcher directly. The delay avoids the navbar to become briefly visible.
|
||||
* The duration
|
||||
* is the same as in SysUI, see http://shortn/_uNSbDoRUSr.
|
||||
*/
|
||||
private static final long TASKBAR_SHOW_DELAY_MS = 250;
|
||||
|
||||
private final AnimatedFloat mIconAlignment =
|
||||
new AnimatedFloat(this::onIconAlignmentRatioChanged);
|
||||
private final AnimatedFloat mIconAlignment = new AnimatedFloat(this::onIconAlignmentRatioChanged);
|
||||
|
||||
private TaskbarControllers mControllers;
|
||||
private AnimatedFloat mTaskbarBackgroundAlpha;
|
||||
@@ -147,7 +159,8 @@ public class TaskbarLauncherStateController {
|
||||
private int mState;
|
||||
private LauncherState mLauncherState = LauncherState.NORMAL;
|
||||
|
||||
// Time when FLAG_TASKBAR_HIDDEN was last cleared, SystemClock.elapsedRealtime (milliseconds).
|
||||
// Time when FLAG_TASKBAR_HIDDEN was last cleared, SystemClock.elapsedRealtime
|
||||
// (milliseconds).
|
||||
private long mLastUnlockTimeMs = 0;
|
||||
|
||||
private @Nullable TaskBarRecentsAnimationListener mTaskBarRecentsAnimationListener;
|
||||
@@ -161,61 +174,62 @@ public class TaskbarLauncherStateController {
|
||||
|
||||
private boolean mIsQsbInline;
|
||||
|
||||
private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
|
||||
new DeviceProfile.OnDeviceProfileChangeListener() {
|
||||
@Override
|
||||
public void onDeviceProfileChanged(DeviceProfile dp) {
|
||||
if (mIsQsbInline && !dp.isQsbInline) {
|
||||
// We only modify QSB alpha if isQsbInline = true. If we switch to a DP
|
||||
// where isQsbInline = false, then we need to reset the alpha.
|
||||
mLauncher.getHotseat().setQsbAlpha(1f);
|
||||
}
|
||||
mIsQsbInline = dp.isQsbInline;
|
||||
TaskbarLauncherStateController.this.updateIconAlphaForHome(
|
||||
mIconAlphaForHome.getValue());
|
||||
}
|
||||
};
|
||||
private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener = new DeviceProfile.OnDeviceProfileChangeListener() {
|
||||
@Override
|
||||
public void onDeviceProfileChanged(DeviceProfile dp) {
|
||||
if (mIsQsbInline && !dp.isQsbInline) {
|
||||
// We only modify QSB alpha if isQsbInline = true. If we switch to a DP
|
||||
// where isQsbInline = false, then we need to reset the alpha.
|
||||
mLauncher.getHotseat().setQsbAlpha(1f);
|
||||
}
|
||||
mIsQsbInline = dp.isQsbInline;
|
||||
TaskbarLauncherStateController.this.updateIconAlphaForHome(
|
||||
mIconAlphaForHome.getValue());
|
||||
}
|
||||
};
|
||||
|
||||
private final StateManager.StateListener<LauncherState> mStateListener =
|
||||
new StateManager.StateListener<LauncherState>() {
|
||||
private final StateManager.StateListener<LauncherState> mStateListener = new StateManager.StateListener<LauncherState>() {
|
||||
|
||||
@Override
|
||||
public void onStateTransitionStart(LauncherState toState) {
|
||||
if (toState != mLauncherState) {
|
||||
// Treat FLAG_LAUNCHER_IN_STATE_TRANSITION as a changed flag even if a
|
||||
// previous state transition was already running, so we update the new
|
||||
// target.
|
||||
mPrevState &= ~FLAG_LAUNCHER_IN_STATE_TRANSITION;
|
||||
mLauncherState = toState;
|
||||
}
|
||||
updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true);
|
||||
if (!mShouldDelayLauncherStateAnim) {
|
||||
if (toState == LauncherState.NORMAL) {
|
||||
applyState(QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION);
|
||||
} else {
|
||||
applyState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateTransitionComplete(LauncherState finalState) {
|
||||
mLauncherState = finalState;
|
||||
updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, false);
|
||||
// TODO(b/279514548) Cleans up bad state that can occur when user interacts with
|
||||
// taskbar on top of transparent activity.
|
||||
if (finalState == LauncherState.NORMAL && mLauncher.isResumed()) {
|
||||
updateStateForFlag(FLAG_RESUMED, true);
|
||||
}
|
||||
@Override
|
||||
public void onStateTransitionStart(LauncherState toState) {
|
||||
if (toState != mLauncherState) {
|
||||
// Treat FLAG_LAUNCHER_IN_STATE_TRANSITION as a changed flag even if a
|
||||
// previous state transition was already running, so we update the new
|
||||
// target.
|
||||
mPrevState &= ~FLAG_LAUNCHER_IN_STATE_TRANSITION;
|
||||
mLauncherState = toState;
|
||||
}
|
||||
updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true);
|
||||
if (!mShouldDelayLauncherStateAnim) {
|
||||
if (toState == LauncherState.NORMAL) {
|
||||
applyState(QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION);
|
||||
} else {
|
||||
applyState();
|
||||
boolean disallowLongClick = finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
|
||||
com.android.launcher3.taskbar.Utilities.setOverviewDragState(
|
||||
mControllers, finalState.disallowTaskbarGlobalDrag(),
|
||||
disallowLongClick, finalState.allowTaskbarInitialSplitSelection());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** Initializes the controller instance, and applies the initial state immediately. */
|
||||
@Override
|
||||
public void onStateTransitionComplete(LauncherState finalState) {
|
||||
mLauncherState = finalState;
|
||||
updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, false);
|
||||
// TODO(b/279514548) Cleans up bad state that can occur when user interacts with
|
||||
// taskbar on top of transparent activity.
|
||||
if (finalState == LauncherState.NORMAL && mLauncher.hasBeenResumed()) {
|
||||
updateStateForFlag(FLAG_RESUMED, true);
|
||||
}
|
||||
applyState();
|
||||
boolean disallowLongClick = finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
|
||||
com.android.launcher3.taskbar.Utilities.setOverviewDragState(
|
||||
mControllers, finalState.disallowTaskbarGlobalDrag(),
|
||||
disallowLongClick, finalState.allowTaskbarInitialSplitSelection());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the controller instance, and applies the initial state
|
||||
* immediately.
|
||||
*/
|
||||
public void init(TaskbarControllers controllers, QuickstepLauncher launcher,
|
||||
int sysuiStateFlags) {
|
||||
mCanSyncViews = false;
|
||||
@@ -236,7 +250,7 @@ public class TaskbarLauncherStateController {
|
||||
|
||||
mLauncher.getStateManager().addStateListener(mStateListener);
|
||||
mLauncherState = launcher.getStateManager().getState();
|
||||
updateStateForSysuiFlags(sysuiStateFlags, /*applyState*/ false);
|
||||
updateStateForSysuiFlags(sysuiStateFlags, /* applyState */ false);
|
||||
|
||||
applyState(0);
|
||||
|
||||
@@ -249,7 +263,6 @@ public class TaskbarLauncherStateController {
|
||||
|
||||
mIconAlignment.finishAnimation();
|
||||
|
||||
Log.d("b/260135164", "onDestroy - updateIconAlphaForHome(1)");
|
||||
mLauncher.getHotseat().setIconsAlpha(1f);
|
||||
mLauncher.getStateManager().removeStateListener(mStateListener);
|
||||
|
||||
@@ -260,7 +273,8 @@ public class TaskbarLauncherStateController {
|
||||
/**
|
||||
* Creates a transition animation to the launcher activity.
|
||||
*
|
||||
* Warning: the resulting animation must be played, since this method has side effects on this
|
||||
* Warning: the resulting animation must be played, since this method has side
|
||||
* effects on this
|
||||
* controller's state.
|
||||
*/
|
||||
public Animator createAnimToLauncher(@NonNull LauncherState toState,
|
||||
@@ -269,7 +283,8 @@ public class TaskbarLauncherStateController {
|
||||
// If going home, align the icons to hotseat
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
|
||||
// Update stashed flags first to ensure goingToUnstashedLauncherState() returns correctly.
|
||||
// Update stashed flags first to ensure goingToUnstashedLauncherState() returns
|
||||
// correctly.
|
||||
TaskbarStashController stashController = mControllers.taskbarStashController;
|
||||
stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
|
||||
toState.isTaskbarStashed(mLauncher));
|
||||
@@ -288,8 +303,8 @@ public class TaskbarLauncherStateController {
|
||||
}
|
||||
mTaskBarRecentsAnimationListener = new TaskBarRecentsAnimationListener(callbacks);
|
||||
callbacks.addListener(mTaskBarRecentsAnimationListener);
|
||||
((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(() ->
|
||||
mTaskBarRecentsAnimationListener.endGestureStateOverride(true));
|
||||
((RecentsView) mLauncher.getOverviewPanel())
|
||||
.setTaskLaunchListener(() -> mTaskBarRecentsAnimationListener.endGestureStateOverride(true));
|
||||
return animatorSet;
|
||||
}
|
||||
|
||||
@@ -299,7 +314,8 @@ public class TaskbarLauncherStateController {
|
||||
|
||||
public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) {
|
||||
if (!shouldDelayLauncherStateAnim && mShouldDelayLauncherStateAnim) {
|
||||
// Animate the animation we have delayed immediately. This is usually triggered when
|
||||
// Animate the animation we have delayed immediately. This is usually triggered
|
||||
// when
|
||||
// the user has released their finger.
|
||||
applyState();
|
||||
}
|
||||
@@ -311,7 +327,7 @@ public class TaskbarLauncherStateController {
|
||||
updateStateForSysuiFlags(systemUiStateFlags, /* applyState */ true);
|
||||
}
|
||||
|
||||
private void updateStateForSysuiFlags(int systemUiStateFlags, boolean applyState) {
|
||||
private void updateStateForSysuiFlags(int systemUiStateFlags, boolean applyState) {
|
||||
final boolean prevIsAwake = hasAnyFlag(FLAG_AWAKE);
|
||||
final boolean currIsAwake = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_AWAKE);
|
||||
|
||||
@@ -326,10 +342,14 @@ public class TaskbarLauncherStateController {
|
||||
boolean isDeviceLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED);
|
||||
updateStateForFlag(FLAG_DEVICE_LOCKED, isDeviceLocked);
|
||||
|
||||
// Taskbar is hidden whenever the device is dreaming. The dreaming state includes the
|
||||
// interactive dreams, AoD, screen off. Since the SYSUI_STATE_DEVICE_DREAMING only kicks in
|
||||
// when the device is asleep, the second condition extends ensures that the transition from
|
||||
// and to the WAKEFULNESS_ASLEEP state also hide the taskbar, and improves the taskbar
|
||||
// Taskbar is hidden whenever the device is dreaming. The dreaming state
|
||||
// includes the
|
||||
// interactive dreams, AoD, screen off. Since the SYSUI_STATE_DEVICE_DREAMING
|
||||
// only kicks in
|
||||
// when the device is asleep, the second condition extends ensures that the
|
||||
// transition from
|
||||
// and to the WAKEFULNESS_ASLEEP state also hide the taskbar, and improves the
|
||||
// taskbar
|
||||
// hide/reveal animation timings.
|
||||
boolean isTaskbarHidden = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_DEVICE_DREAMING)
|
||||
|| (systemUiStateFlags & SYSUI_STATE_WAKEFULNESS_MASK) != WAKEFULNESS_AWAKE;
|
||||
@@ -343,7 +363,8 @@ public class TaskbarLauncherStateController {
|
||||
/**
|
||||
* 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.
|
||||
* 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
|
||||
@@ -409,6 +430,14 @@ public class TaskbarLauncherStateController {
|
||||
+ ", mLauncherState: " + mLauncherState
|
||||
+ ", toAlignment: " + toAlignment);
|
||||
}
|
||||
mControllers.bubbleControllers.ifPresent(controllers -> {
|
||||
// Show the bubble bar when on launcher home or in overview.
|
||||
boolean onHome = isInLauncher && mLauncherState == LauncherState.NORMAL;
|
||||
boolean onOverview = mLauncherState == LauncherState.OVERVIEW;
|
||||
controllers.bubbleStashController.setBubblesShowingOnHome(onHome);
|
||||
controllers.bubbleStashController.setBubblesShowingOnOverview(onOverview);
|
||||
});
|
||||
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
|
||||
if (hasAnyFlag(changedFlags, FLAG_LAUNCHER_IN_STATE_TRANSITION)) {
|
||||
@@ -426,7 +455,8 @@ public class TaskbarLauncherStateController {
|
||||
handleOpenFloatingViews = true;
|
||||
}
|
||||
if (mLauncherState == LauncherState.OVERVIEW) {
|
||||
// Calling to update the insets in TaskbarInsetController#updateInsetsTouchability
|
||||
// Calling to update the insets in
|
||||
// TaskbarInsetController#updateInsetsTouchability
|
||||
mControllers.taskbarActivityContext.notifyUpdateLayoutParams();
|
||||
}
|
||||
}
|
||||
@@ -437,8 +467,7 @@ public class TaskbarLauncherStateController {
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mIsAnimatingToLauncher = isInLauncher;
|
||||
|
||||
TaskbarStashController stashController =
|
||||
mControllers.taskbarStashController;
|
||||
TaskbarStashController stashController = mControllers.taskbarStashController;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onAnimationStart - FLAG_IN_APP: " + !isInLauncher);
|
||||
}
|
||||
@@ -477,13 +506,14 @@ public class TaskbarLauncherStateController {
|
||||
animatorSet.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
TaskbarStashController stashController =
|
||||
mControllers.taskbarStashController;
|
||||
stashController.updateAndAnimateTransientTaskbar(/* stash */ true);
|
||||
TaskbarStashController stashController = mControllers.taskbarStashController;
|
||||
stashController.updateAndAnimateTransientTaskbar(
|
||||
/* stash */ true, /* bubblesShouldFollow */ true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// delay the fade in animation a bit to reduce visual noise when waking up a device
|
||||
// delay the fade in animation a bit to reduce visual noise when waking up a
|
||||
// device
|
||||
// with a fingerprint reader. This should only be done when the device was woken
|
||||
// up via fingerprint reader, however since this information is currently not
|
||||
// available, opting to always delay the fade-in a bit.
|
||||
@@ -511,7 +541,8 @@ public class TaskbarLauncherStateController {
|
||||
boolean isInLauncherIconIsAligned = isInLauncher && isIconAlignedWithHotseat;
|
||||
|
||||
float startDelay = 0;
|
||||
// We want to delay the background from fading in so that the icons have time to move
|
||||
// We want to delay the background from fading in so that the icons have time to
|
||||
// move
|
||||
// into the bounds of the background before it appears.
|
||||
if (isInLauncherIconNotAligned) {
|
||||
startDelay = duration * TASKBAR_BG_ALPHA_LAUNCHER_NOT_ALIGNED_DELAY_MULT;
|
||||
@@ -547,13 +578,14 @@ public class TaskbarLauncherStateController {
|
||||
|
||||
// Keep isUnlockTransition in sync with its counterpart in
|
||||
// TaskbarStashController#createAnimToIsStashed.
|
||||
boolean isUnlockTransition =
|
||||
hasAnyFlag(changedFlags, FLAG_DEVICE_LOCKED) && !hasAnyFlag(FLAG_DEVICE_LOCKED);
|
||||
boolean isUnlockTransition = hasAnyFlag(changedFlags, FLAG_DEVICE_LOCKED) && !hasAnyFlag(FLAG_DEVICE_LOCKED);
|
||||
if (isUnlockTransition) {
|
||||
// When transitioning to unlocked, ensure the hotseat is fully visible from the
|
||||
// beginning. The hotseat itself is animated by LauncherUnlockAnimationController.
|
||||
// beginning. The hotseat itself is animated by
|
||||
// LauncherUnlockAnimationController.
|
||||
mIconAlignment.cancelAnimation();
|
||||
// updateValue ensures onIconAlignmentRatioChanged will be called if there is an actual
|
||||
// updateValue ensures onIconAlignmentRatioChanged will be called if there is an
|
||||
// actual
|
||||
// change in value
|
||||
mIconAlignment.updateValue(toAlignment);
|
||||
|
||||
@@ -589,9 +621,11 @@ public class TaskbarLauncherStateController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the taskbar is aligned with the hotseat in the current/target launcher state.
|
||||
* Whether the taskbar is aligned with the hotseat in the current/target
|
||||
* launcher state.
|
||||
*
|
||||
* This refers to the intended state - a transition to this state might be in progress.
|
||||
* This refers to the intended state - a transition to this state might be in
|
||||
* progress.
|
||||
*/
|
||||
public boolean isTaskbarAlignedWithHotseat() {
|
||||
return mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
|
||||
@@ -605,8 +639,7 @@ public class TaskbarLauncherStateController {
|
||||
boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
|
||||
boolean willStashVisually = isInStashedState
|
||||
&& mControllers.taskbarStashController.supportsVisualStashing();
|
||||
boolean isTaskbarAlignedWithHotseat =
|
||||
mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
|
||||
boolean isTaskbarAlignedWithHotseat = mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
|
||||
return isTaskbarAlignedWithHotseat && !willStashVisually;
|
||||
} else {
|
||||
return false;
|
||||
@@ -636,8 +669,6 @@ public class TaskbarLauncherStateController {
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (isInStashedState && committed) {
|
||||
// Reset hotseat alpha to default
|
||||
Log.d("b/260135164",
|
||||
"playStateTransitionAnim#onAnimationEnd - setIconsAlpha(1)");
|
||||
mLauncher.getHotseat().setIconsAlpha(1);
|
||||
}
|
||||
}
|
||||
@@ -652,9 +683,12 @@ public class TaskbarLauncherStateController {
|
||||
animatorSet.play(stashAnimator);
|
||||
}
|
||||
|
||||
// Translate back to 0 at a shorter or same duration as the icon alignment animation.
|
||||
// This ensures there is no jump after switching to hotseat, e.g. when swiping up from
|
||||
// overview to home. When not in app, we do duration / 2 just to make it feel snappier.
|
||||
// Translate back to 0 at a shorter or same duration as the icon alignment
|
||||
// animation.
|
||||
// This ensures there is no jump after switching to hotseat, e.g. when swiping
|
||||
// up from
|
||||
// overview to home. When not in app, we do duration / 2 just to make it feel
|
||||
// snappier.
|
||||
long resetDuration = mControllers.taskbarStashController.isInApp()
|
||||
? duration
|
||||
: duration / 2;
|
||||
@@ -705,21 +739,16 @@ public class TaskbarLauncherStateController {
|
||||
|
||||
private void updateIconAlphaForHome(float alpha) {
|
||||
if (mControllers.taskbarActivityContext.isDestroyed()) {
|
||||
Log.e("b/260135164", "updateIconAlphaForHome is called after Taskbar is destroyed",
|
||||
new Exception());
|
||||
return;
|
||||
}
|
||||
mIconAlphaForHome.setValue(alpha);
|
||||
boolean hotseatVisible = alpha == 0
|
||||
|| (!mControllers.uiController.isHotseatIconOnTopWhenAligned()
|
||||
&& mIconAlignment.value > 0);
|
||||
&& mIconAlignment.value > 0);
|
||||
/*
|
||||
* Hide Launcher Hotseat icons when Taskbar icons have opacity. Both icon sets
|
||||
* should not be visible at the same time.
|
||||
*/
|
||||
Log.d("b/260135164",
|
||||
"updateIconAlphaForHome - setIconsAlpha(" + (hotseatVisible ? 1 : 0)
|
||||
+ "), isTaskbarPresent: " + mLauncher.getDeviceProfile().isTaskbarPresent);
|
||||
mLauncher.getHotseat().setIconsAlpha(hotseatVisible ? 1 : 0);
|
||||
if (mIsQsbInline) {
|
||||
mLauncher.getHotseat().setQsbAlpha(hotseatVisible ? 1 : 0);
|
||||
|
||||
@@ -22,13 +22,13 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
|
||||
|
||||
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
|
||||
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentCallbacks;
|
||||
import android.content.Context;
|
||||
@@ -41,6 +41,7 @@ import android.hardware.display.DisplayManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.Trace;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
@@ -50,21 +51,20 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.statemanager.StatefulActivity;
|
||||
import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.NavigationMode;
|
||||
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
|
||||
import com.android.launcher3.util.SettingsCache;
|
||||
import com.android.launcher3.util.SimpleBroadcastReceiver;
|
||||
import com.android.quickstep.RecentsActivity;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.TouchInteractionService;
|
||||
import com.android.quickstep.util.AssistUtils;
|
||||
import com.android.systemui.shared.system.QuickStepContract;
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
|
||||
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
|
||||
@@ -79,6 +79,22 @@ public class TaskbarManager {
|
||||
private static final String TAG = "TaskbarManager";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
/**
|
||||
* All the configurations which do not initiate taskbar recreation.
|
||||
* This includes all the configurations defined in Launcher's manifest entry and
|
||||
* ActivityController#filterConfigChanges
|
||||
*/
|
||||
private static final int SKIP_RECREATE_CONFIG_CHANGES = ActivityInfo.CONFIG_WINDOW_CONFIGURATION
|
||||
| ActivityInfo.CONFIG_KEYBOARD
|
||||
| ActivityInfo.CONFIG_KEYBOARD_HIDDEN
|
||||
| ActivityInfo.CONFIG_MCC
|
||||
| ActivityInfo.CONFIG_MNC
|
||||
| ActivityInfo.CONFIG_NAVIGATION
|
||||
| ActivityInfo.CONFIG_ORIENTATION
|
||||
| ActivityInfo.CONFIG_SCREEN_SIZE
|
||||
| ActivityInfo.CONFIG_SCREEN_LAYOUT
|
||||
| ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
|
||||
|
||||
public static final boolean FLAG_HIDE_NAVBAR_WINDOW = SystemProperties
|
||||
.getBoolean("persist.wm.debug.hide_navbar_window", false);
|
||||
|
||||
@@ -89,12 +105,11 @@ public class TaskbarManager {
|
||||
Settings.Secure.NAV_BAR_KIDS_MODE);
|
||||
|
||||
private final Context mContext;
|
||||
private final DisplayController mDisplayController;
|
||||
private final TaskbarNavButtonController mNavButtonController;
|
||||
private final SettingsCache.OnChangeListener mUserSetupCompleteListener;
|
||||
private final SettingsCache.OnChangeListener mNavBarKidsModeListener;
|
||||
private final ComponentCallbacks mComponentCallbacks;
|
||||
private final SimpleBroadcastReceiver mShutdownReceiver;
|
||||
|
||||
private final SimpleBroadcastReceiver mShutdownReceiver = new SimpleBroadcastReceiver(
|
||||
i -> destroyExistingTaskbar());
|
||||
|
||||
// The source for this provider is set when Launcher is available
|
||||
// We use 'non-destroyable' version here so the original provider won't be
|
||||
@@ -102,7 +117,6 @@ public class TaskbarManager {
|
||||
// as it is tied to the activity lifecycle, not the taskbar lifecycle.
|
||||
// It's destruction/creation will be managed by the activity.
|
||||
private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider = new NonDestroyableScopedUnfoldTransitionProgressProvider();
|
||||
private NavigationMode mNavMode;
|
||||
|
||||
private TaskbarActivityContext mTaskbarActivityContext;
|
||||
private StatefulActivity mActivity;
|
||||
@@ -114,24 +128,12 @@ public class TaskbarManager {
|
||||
private final TaskbarSharedState mSharedState = new TaskbarSharedState();
|
||||
|
||||
/**
|
||||
* We use WindowManager's ComponentCallbacks() for most of the config changes,
|
||||
* however for
|
||||
* navigation mode, that callback gets called too soon, before it's internal
|
||||
* navigation mode
|
||||
* reflects the current one.
|
||||
* DisplayController's callback is delayed enough to get the correct nav mode
|
||||
* value
|
||||
*
|
||||
* We also use density change here because DeviceProfile has had a chance to
|
||||
* update it's state
|
||||
* whereas density for component callbacks registered in this class don't update
|
||||
* DeviceProfile.
|
||||
* Confused? Me too. Make it less confusing (TODO: b/227669780)
|
||||
*
|
||||
* Flags used with {@link #mDispInfoChangeListener}
|
||||
* We use WindowManager's ComponentCallbacks() for internal UI changes (similar
|
||||
* to an Activity)
|
||||
* which comes via a different channel
|
||||
*/
|
||||
private static final int CHANGE_FLAGS = CHANGE_NAVIGATION_MODE | CHANGE_DENSITY;
|
||||
private final DisplayController.DisplayInfoChangeListener mDispInfoChangeListener;
|
||||
private final OnIDPChangeListener mIdpChangeListener = c -> recreateTaskbar();
|
||||
private final SettingsCache.OnChangeListener mOnSettingsChangeListener = c -> recreateTaskbar();
|
||||
|
||||
private boolean mUserUnlocked = false;
|
||||
|
||||
@@ -152,101 +154,120 @@ public class TaskbarManager {
|
||||
}
|
||||
};
|
||||
|
||||
private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
|
||||
@Override
|
||||
public void onActivityDestroyed(Activity activity) {
|
||||
if (mActivity != activity)
|
||||
return;
|
||||
if (mActivity != null) {
|
||||
mActivity.removeOnDeviceProfileChangeListener(
|
||||
mDebugActivityDeviceProfileChanged);
|
||||
Log.d(TASKBAR_NOT_DESTROYED_TAG,
|
||||
"unregistering activity lifecycle callbacks from "
|
||||
+ "onActivityDestroyed.");
|
||||
mActivity.unregisterActivityLifecycleCallbacks(this);
|
||||
}
|
||||
mActivity = null;
|
||||
debugWhyTaskbarNotDestroyed("clearActivity");
|
||||
if (mTaskbarActivityContext != null) {
|
||||
mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
|
||||
}
|
||||
mUnfoldProgressProvider.setSourceProvider(null);
|
||||
}
|
||||
};
|
||||
|
||||
UnfoldTransitionProgressProvider.TransitionProgressListener mUnfoldTransitionProgressListener = new UnfoldTransitionProgressProvider.TransitionProgressListener() {
|
||||
@Override
|
||||
public void onTransitionStarted() {
|
||||
Log.d(TASKBAR_NOT_DESTROYED_TAG,
|
||||
"fold/unfold transition started getting called.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionProgress(float progress) {
|
||||
Log.d(TASKBAR_NOT_DESTROYED_TAG,
|
||||
"fold/unfold transition progress : " + progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionFinishing() {
|
||||
Log.d(TASKBAR_NOT_DESTROYED_TAG,
|
||||
"fold/unfold transition finishing getting called.");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionFinished() {
|
||||
Log.d(TASKBAR_NOT_DESTROYED_TAG,
|
||||
"fold/unfold transition finished getting called.");
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
public TaskbarManager(TouchInteractionService service) {
|
||||
mDisplayController = DisplayController.INSTANCE.get(service);
|
||||
Display display = service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
|
||||
mContext = Utilities.ATLEAST_S
|
||||
? service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
|
||||
: null;
|
||||
mContext = service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null);
|
||||
mNavButtonController = new TaskbarNavButtonController(service,
|
||||
SystemUiProxy.INSTANCE.get(mContext), new Handler());
|
||||
mUserSetupCompleteListener = isUserSetupComplete -> recreateTaskbar();
|
||||
mNavBarKidsModeListener = isNavBarKidsMode -> recreateTaskbar();
|
||||
// TODO(b/227669780): Consolidate this w/ DisplayController callbacks
|
||||
SystemUiProxy.INSTANCE.get(mContext), new Handler(),
|
||||
AssistUtils.newInstance(mContext));
|
||||
mComponentCallbacks = new ComponentCallbacks() {
|
||||
private Configuration mOldConfig = mContext.getResources().getConfiguration();
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
Trace.instantForTrack(Trace.TRACE_TAG_APP, "TaskbarManager",
|
||||
"onConfigurationChanged: " + newConfig);
|
||||
debugWhyTaskbarNotDestroyed(
|
||||
"TaskbarManager#mComponentCallbacks.onConfigurationChanged: " + newConfig);
|
||||
DeviceProfile dp = mUserUnlocked
|
||||
? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext)
|
||||
: null;
|
||||
int configDiff = mOldConfig.diff(newConfig);
|
||||
int configDiffForRecreate = configDiff;
|
||||
int configsRequiringRecreate = ActivityInfo.CONFIG_ASSETS_PATHS
|
||||
| ActivityInfo.CONFIG_LAYOUT_DIRECTION | ActivityInfo.CONFIG_UI_MODE
|
||||
| ActivityInfo.CONFIG_SCREEN_SIZE;
|
||||
if ((configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0
|
||||
&& mTaskbarActivityContext != null && dp != null
|
||||
&& !isPhoneMode(dp)) {
|
||||
// Additional check since this callback gets fired multiple times w/o
|
||||
// screen size changing, or when simply rotating the device.
|
||||
// In the case of phone device rotation, we do want to call recreateTaskbar()
|
||||
DeviceProfile oldDp = mTaskbarActivityContext.getDeviceProfile();
|
||||
boolean isOrientationChange = (configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0;
|
||||
int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
|
||||
|
||||
int newOrientation = newConfig.windowConfiguration.getRotation();
|
||||
int oldOrientation = mOldConfig.windowConfiguration.getRotation();
|
||||
int oldWidth = isOrientationChange ? oldDp.heightPx : oldDp.widthPx;
|
||||
int oldHeight = isOrientationChange ? oldDp.widthPx : oldDp.heightPx;
|
||||
|
||||
if ((dp.widthPx == oldWidth && dp.heightPx == oldHeight)
|
||||
|| (newOrientation == oldOrientation)) {
|
||||
configDiffForRecreate &= ~ActivityInfo.CONFIG_SCREEN_SIZE;
|
||||
}
|
||||
}
|
||||
if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
|
||||
// Only recreate for theme changes, not other UI mode changes such as docking.
|
||||
int oldUiNightMode = (mOldConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
|
||||
int newUiNightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
|
||||
if (oldUiNightMode == newUiNightMode) {
|
||||
configDiffForRecreate &= ~ActivityInfo.CONFIG_UI_MODE;
|
||||
configDiff &= ~ActivityInfo.CONFIG_UI_MODE;
|
||||
}
|
||||
}
|
||||
|
||||
debugWhyTaskbarNotDestroyed("ComponentCallbacks#onConfigurationChanged() "
|
||||
+ "configDiffForRecreate="
|
||||
+ Configuration.configurationDiffToString(configDiffForRecreate));
|
||||
if ((configDiffForRecreate & configsRequiringRecreate) != 0) {
|
||||
+ "configDiff=" + Configuration.configurationDiffToString(configDiff));
|
||||
if (configDiff != 0 || mTaskbarActivityContext == null) {
|
||||
recreateTaskbar();
|
||||
} else {
|
||||
// Config change might be handled without re-creating the taskbar
|
||||
if (mTaskbarActivityContext != null) {
|
||||
if (dp != null && !isTaskbarPresent(dp)) {
|
||||
destroyExistingTaskbar();
|
||||
} else {
|
||||
if (dp != null && isTaskbarPresent(dp)) {
|
||||
mTaskbarActivityContext.updateDeviceProfile(dp, mNavMode);
|
||||
if (dp != null && !isTaskbarPresent(dp)) {
|
||||
destroyExistingTaskbar();
|
||||
} else {
|
||||
if (dp != null && isTaskbarPresent(dp)) {
|
||||
if (FLAG_HIDE_NAVBAR_WINDOW) {
|
||||
// Re-initialize for screen size change? Should this be done
|
||||
// by looking at screen-size change flag in configDiff in the
|
||||
// block above?
|
||||
recreateTaskbar();
|
||||
} else {
|
||||
mTaskbarActivityContext.updateDeviceProfile(dp);
|
||||
}
|
||||
mTaskbarActivityContext.onConfigurationChanged(configDiff);
|
||||
}
|
||||
mTaskbarActivityContext.onConfigurationChanged(configDiff);
|
||||
}
|
||||
}
|
||||
mOldConfig = newConfig;
|
||||
mOldConfig = new Configuration(newConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
}
|
||||
};
|
||||
mShutdownReceiver = new SimpleBroadcastReceiver(i -> destroyExistingTaskbar());
|
||||
mDispInfoChangeListener = (context, info, flags) -> {
|
||||
if ((flags & CHANGE_FLAGS) != 0) {
|
||||
mNavMode = info.navigationMode;
|
||||
recreateTaskbar();
|
||||
}
|
||||
debugWhyTaskbarNotDestroyed("DisplayInfoChangeListener#"
|
||||
+ mDisplayController.getChangeFlagsString(flags));
|
||||
};
|
||||
mNavMode = mDisplayController.getInfo().navigationMode;
|
||||
mDisplayController.addChangeListener(mDispInfoChangeListener);
|
||||
SettingsCache.INSTANCE.get(mContext).register(USER_SETUP_COMPLETE_URI,
|
||||
mUserSetupCompleteListener);
|
||||
SettingsCache.INSTANCE.get(mContext).register(NAV_BAR_KIDS_MODE,
|
||||
mNavBarKidsModeListener);
|
||||
SettingsCache.INSTANCE.get(mContext)
|
||||
.register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
|
||||
SettingsCache.INSTANCE.get(mContext)
|
||||
.register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
|
||||
Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
|
||||
mContext.registerComponentCallbacks(mComponentCallbacks);
|
||||
mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN);
|
||||
UI_HELPER_EXECUTOR.execute(() -> {
|
||||
@@ -286,6 +307,26 @@ public class TaskbarManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles All Apps for Taskbar or Launcher depending on the current state.
|
||||
*
|
||||
* @param homeAllAppsIntent Intent used if Taskbar is not enabled or Launcher is
|
||||
* resumed.
|
||||
*/
|
||||
public void toggleAllApps(Intent homeAllAppsIntent) {
|
||||
if (mTaskbarActivityContext == null) {
|
||||
mContext.startActivity(homeAllAppsIntent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mActivity != null && mActivity.isResumed() && !mActivity.isInState(OVERVIEW)) {
|
||||
mContext.startActivity(homeAllAppsIntent);
|
||||
return;
|
||||
}
|
||||
|
||||
mTaskbarActivityContext.toggleAllApps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a frame of the first Launcher reveal animation.
|
||||
*
|
||||
@@ -304,6 +345,7 @@ public class TaskbarManager {
|
||||
*/
|
||||
public void onUserUnlocked() {
|
||||
mUserUnlocked = true;
|
||||
LauncherAppState.getIDP(mContext).addOnChangeListener(mIdpChangeListener);
|
||||
recreateTaskbar();
|
||||
}
|
||||
|
||||
@@ -318,14 +360,18 @@ public class TaskbarManager {
|
||||
if (mActivity == activity) {
|
||||
return;
|
||||
}
|
||||
if (mActivity != null) {
|
||||
mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
|
||||
}
|
||||
removeActivityCallbacksAndListeners();
|
||||
mActivity = activity;
|
||||
debugWhyTaskbarNotDestroyed("Set mActivity=" + mActivity);
|
||||
mActivity.addOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
|
||||
Log.d(TASKBAR_NOT_DESTROYED_TAG,
|
||||
"registering activity lifecycle callbacks from setActivity().");
|
||||
mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
|
||||
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider = getUnfoldTransitionProgressProviderForActivity(
|
||||
activity);
|
||||
if (unfoldTransitionProgressProvider != null) {
|
||||
unfoldTransitionProgressProvider.addCallback(mUnfoldTransitionProgressListener);
|
||||
}
|
||||
mUnfoldProgressProvider.setSourceProvider(unfoldTransitionProgressProvider);
|
||||
|
||||
if (mTaskbarActivityContext != null) {
|
||||
@@ -364,21 +410,6 @@ public class TaskbarManager {
|
||||
return TaskbarUIController.DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears a previously set {@link StatefulActivity}
|
||||
*/
|
||||
public void clearActivity(@NonNull StatefulActivity activity) {
|
||||
if (mActivity == activity) {
|
||||
mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
|
||||
mActivity = null;
|
||||
debugWhyTaskbarNotDestroyed("clearActivity");
|
||||
if (mTaskbarActivityContext != null) {
|
||||
mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
|
||||
}
|
||||
mUnfoldProgressProvider.setSourceProvider(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called multiple times (ex. initial init, then when user
|
||||
* unlocks) in which case
|
||||
@@ -387,32 +418,42 @@ public class TaskbarManager {
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void recreateTaskbar() {
|
||||
DeviceProfile dp = mUserUnlocked ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
|
||||
Trace.beginSection("recreateTaskbar");
|
||||
try {
|
||||
DeviceProfile dp = mUserUnlocked ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
|
||||
|
||||
destroyExistingTaskbar();
|
||||
destroyExistingTaskbar();
|
||||
|
||||
boolean isTaskbarEnabled = dp != null && isTaskbarPresent(dp);
|
||||
debugWhyTaskbarNotDestroyed("recreateTaskbar: isTaskbarEnabled=" + isTaskbarEnabled
|
||||
+ " [dp != null (i.e. mUserUnlocked)]=" + (dp != null)
|
||||
+ " FLAG_HIDE_NAVBAR_WINDOW=" + FLAG_HIDE_NAVBAR_WINDOW
|
||||
+ " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent));
|
||||
if (!isTaskbarEnabled) {
|
||||
SystemUiProxy.INSTANCE.get(mContext)
|
||||
.notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
|
||||
return;
|
||||
}
|
||||
boolean isTaskbarEnabled = dp != null && isTaskbarPresent(dp);
|
||||
debugWhyTaskbarNotDestroyed("recreateTaskbar: isTaskbarEnabled=" + isTaskbarEnabled
|
||||
+ " [dp != null (i.e. mUserUnlocked)]=" + (dp != null)
|
||||
+ " FLAG_HIDE_NAVBAR_WINDOW=" + FLAG_HIDE_NAVBAR_WINDOW
|
||||
+ " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent));
|
||||
if (!isTaskbarEnabled) {
|
||||
SystemUiProxy.INSTANCE.get(mContext)
|
||||
.notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mTaskbarActivityContext == null) {
|
||||
mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp, mNavButtonController,
|
||||
mUnfoldProgressProvider);
|
||||
} else {
|
||||
mTaskbarActivityContext.updateDeviceProfile(dp, mNavMode);
|
||||
}
|
||||
mTaskbarActivityContext.init(mSharedState);
|
||||
if (mTaskbarActivityContext == null) {
|
||||
mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp,
|
||||
mNavButtonController,
|
||||
mUnfoldProgressProvider);
|
||||
} else {
|
||||
mTaskbarActivityContext.updateDeviceProfile(dp);
|
||||
}
|
||||
mTaskbarActivityContext.init(mSharedState);
|
||||
|
||||
if (mActivity != null) {
|
||||
mTaskbarActivityContext.setUIController(
|
||||
createTaskbarUIControllerForActivity(mActivity));
|
||||
if (mActivity != null) {
|
||||
mTaskbarActivityContext.setUIController(
|
||||
createTaskbarUIControllerForActivity(mActivity));
|
||||
}
|
||||
|
||||
// We to wait until user unlocks the device to attach listener.
|
||||
LauncherPrefs.get(mContext).addListener(mTaskbarPinningPreferenceChangeListener,
|
||||
TASKBAR_PINNING);
|
||||
} finally {
|
||||
Trace.endSection();
|
||||
}
|
||||
|
||||
// We to wait until user unlocks the device to attach listener.
|
||||
@@ -498,23 +539,38 @@ public class TaskbarManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void removeActivityCallbacksAndListeners() {
|
||||
if (mActivity != null) {
|
||||
mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
|
||||
Log.d(TASKBAR_NOT_DESTROYED_TAG,
|
||||
"unregistering activity lifecycle callbacks from "
|
||||
+ "removeActivityCallbackAndListeners().");
|
||||
mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
|
||||
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider = getUnfoldTransitionProgressProviderForActivity(
|
||||
mActivity);
|
||||
if (unfoldTransitionProgressProvider != null) {
|
||||
unfoldTransitionProgressProvider.removeCallback(mUnfoldTransitionProgressListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the manager is no longer needed
|
||||
*/
|
||||
public void destroy() {
|
||||
debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
|
||||
if (mActivity != null) {
|
||||
mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
|
||||
}
|
||||
|
||||
removeActivityCallbacksAndListeners();
|
||||
UI_HELPER_EXECUTOR.execute(
|
||||
() -> mTaskbarBroadcastReceiver.unregisterReceiverSafely(mContext));
|
||||
destroyExistingTaskbar();
|
||||
mDisplayController.removeChangeListener(mDispInfoChangeListener);
|
||||
SettingsCache.INSTANCE.get(mContext).unregister(USER_SETUP_COMPLETE_URI,
|
||||
mUserSetupCompleteListener);
|
||||
SettingsCache.INSTANCE.get(mContext).unregister(NAV_BAR_KIDS_MODE,
|
||||
mNavBarKidsModeListener);
|
||||
if (mUserUnlocked) {
|
||||
LauncherAppState.getIDP(mContext).removeOnChangeListener(mIdpChangeListener);
|
||||
}
|
||||
SettingsCache.INSTANCE.get(mContext)
|
||||
.unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
|
||||
SettingsCache.INSTANCE.get(mContext)
|
||||
.unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
|
||||
Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
|
||||
mContext.unregisterComponentCallbacks(mComponentCallbacks);
|
||||
mContext.unregisterReceiver(mShutdownReceiver);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public class TaskbarModelCallbacks implements
|
||||
private final TaskbarView mContainer;
|
||||
|
||||
// Initialized in init.
|
||||
private TaskbarControllers mControllers;
|
||||
protected TaskbarControllers mControllers;
|
||||
|
||||
// Used to defer any UI updates during the SUW unstash animation.
|
||||
private boolean mDeferUpdatesForSUW;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 com.android.launcher3.R
|
||||
import com.android.launcher3.util.ResourceBasedOverride
|
||||
import com.android.launcher3.util.ResourceBasedOverride.Overrides
|
||||
|
||||
/** Creates [TaskbarModelCallbacks] instances. */
|
||||
open class TaskbarModelCallbacksFactory : ResourceBasedOverride {
|
||||
|
||||
open fun create(
|
||||
activityContext: TaskbarActivityContext,
|
||||
container: TaskbarView,
|
||||
): TaskbarModelCallbacks = TaskbarModelCallbacks(activityContext, container)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newInstance(context: Context): TaskbarModelCallbacksFactory {
|
||||
return Overrides.getObject(
|
||||
TaskbarModelCallbacksFactory::class.java,
|
||||
context,
|
||||
R.string.taskbar_model_callbacks_factory_class,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,12 +43,16 @@ import androidx.annotation.StringRes;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.logging.StatsLogManager;
|
||||
import com.android.launcher3.statehandlers.DesktopVisibilityController;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.quickstep.LauncherActivityInterface;
|
||||
import com.android.quickstep.OverviewCommandHelper;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.TaskUtils;
|
||||
import com.android.quickstep.TouchInteractionService;
|
||||
import com.android.quickstep.util.AssistUtils;
|
||||
import com.android.quickstep.views.DesktopTaskView;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -105,15 +109,17 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
|
||||
private final TouchInteractionService mService;
|
||||
private final SystemUiProxy mSystemUiProxy;
|
||||
private final Handler mHandler;
|
||||
private final AssistUtils mAssistUtils;
|
||||
@Nullable private StatsLogManager mStatsLogManager;
|
||||
|
||||
private final Runnable mResetLongPress = this::resetScreenUnpin;
|
||||
|
||||
public TaskbarNavButtonController(TouchInteractionService service,
|
||||
SystemUiProxy systemUiProxy, Handler handler) {
|
||||
SystemUiProxy systemUiProxy, Handler handler, AssistUtils assistUtils) {
|
||||
mService = service;
|
||||
mSystemUiProxy = systemUiProxy;
|
||||
mHandler = handler;
|
||||
mAssistUtils = assistUtils;
|
||||
}
|
||||
|
||||
public void onButtonClick(@TaskbarButton int buttonType, View view) {
|
||||
@@ -155,7 +161,7 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
|
||||
switch (buttonType) {
|
||||
case BUTTON_HOME:
|
||||
logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS);
|
||||
startAssistant();
|
||||
onLongPressHome();
|
||||
return true;
|
||||
case BUTTON_A11Y:
|
||||
logEvent(LAUNCHER_TASKBAR_A11Y_BUTTON_LONGPRESS);
|
||||
@@ -267,6 +273,15 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
|
||||
|
||||
private void navigateHome() {
|
||||
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
|
||||
|
||||
if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
|
||||
DesktopVisibilityController desktopVisibilityController =
|
||||
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
|
||||
if (desktopVisibilityController != null) {
|
||||
desktopVisibilityController.onHomeActionTriggered();
|
||||
}
|
||||
}
|
||||
|
||||
mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_HOME);
|
||||
}
|
||||
|
||||
@@ -295,13 +310,16 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
|
||||
}
|
||||
}
|
||||
|
||||
private void startAssistant() {
|
||||
private void onLongPressHome() {
|
||||
if (mScreenPinned || !mAssistantLongPressEnabled) {
|
||||
return;
|
||||
}
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
|
||||
mSystemUiProxy.startAssistant(args);
|
||||
// Attempt to start Assist with AssistUtils, otherwise fall back to SysUi's implementation.
|
||||
if (!mAssistUtils.tryStartAssistOverride(INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS)) {
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
|
||||
mSystemUiProxy.startAssistant(args);
|
||||
}
|
||||
}
|
||||
|
||||
private void showQuickSettings() {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.launcher3.taskbar.bubbles.BubbleBarController.BUBBLE_BAR_ENABLED;
|
||||
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;
|
||||
|
||||
@@ -23,6 +24,7 @@ import android.view.animation.Interpolator;
|
||||
import android.view.animation.PathInterpolator;
|
||||
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
@@ -63,6 +65,10 @@ public class TaskbarScrimViewController implements TaskbarControllers.LoggableTa
|
||||
* Updates the scrim state based on the flags.
|
||||
*/
|
||||
public void updateStateForSysuiFlags(int stateFlags, boolean skipAnim) {
|
||||
if (BUBBLE_BAR_ENABLED && DisplayController.isTransientTaskbar(mActivity)) {
|
||||
// These scrims aren't used if bubble bar & transient taskbar are active.
|
||||
return;
|
||||
}
|
||||
final boolean bubblesExpanded = (stateFlags & SYSUI_STATE_BUBBLES_EXPANDED) != 0;
|
||||
final boolean manageMenuExpanded =
|
||||
(stateFlags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
|
||||
|
||||
@@ -18,11 +18,11 @@ package com.android.launcher3.taskbar;
|
||||
import static android.view.HapticFeedbackConstants.LONG_PRESS;
|
||||
import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
|
||||
|
||||
import static com.android.app.animation.Interpolators.EMPHASIZED;
|
||||
import static com.android.app.animation.Interpolators.FINAL_FRAME;
|
||||
import static com.android.app.animation.Interpolators.INSTANT;
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
|
||||
import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
|
||||
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.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
|
||||
@@ -77,7 +77,8 @@ import java.util.StringJoiner;
|
||||
import java.util.function.IntPredicate;
|
||||
|
||||
/**
|
||||
* Coordinates between controllers such as TaskbarViewController and StashedHandleViewController to
|
||||
* Coordinates between controllers such as TaskbarViewController and
|
||||
* StashedHandleViewController to
|
||||
* create a cohesive animation between stashed/unstashed states.
|
||||
*/
|
||||
public class TaskbarStashController implements TaskbarControllers.LoggableTaskbarController {
|
||||
@@ -94,22 +95,27 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
public static final int FLAG_IN_SETUP = 1 << 7; // In the Setup Wizard
|
||||
public static final int FLAG_STASHED_SMALL_SCREEN = 1 << 8; // phone screen gesture nav, stashed
|
||||
public static final int FLAG_STASHED_IN_APP_AUTO = 1 << 9; // Autohide (transient taskbar).
|
||||
public static final int FLAG_STASHED_SYSUI = 1 << 10; // app pinning,...
|
||||
public static final int FLAG_STASHED_SYSUI = 1 << 10; // app pinning,...
|
||||
public static final int FLAG_STASHED_DEVICE_LOCKED = 1 << 11; // device is locked: keyguard, ...
|
||||
|
||||
// If any of these flags are enabled, isInApp should return true.
|
||||
private static final int FLAGS_IN_APP = FLAG_IN_APP | FLAG_IN_SETUP;
|
||||
|
||||
// If we're in an app and any of these flags are enabled, taskbar should be stashed.
|
||||
// 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_SYSUI | FLAG_STASHED_IN_APP_SETUP
|
||||
| FLAG_STASHED_IN_APP_IME | FLAG_STASHED_IN_TASKBAR_ALL_APPS
|
||||
| FLAG_STASHED_SMALL_SCREEN | FLAG_STASHED_IN_APP_AUTO;
|
||||
|
||||
// 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 or All Apps
|
||||
// since those cover the underlying app anyway and thus the app shouldn't change insets.
|
||||
// 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 or All Apps
|
||||
// since those cover 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 & ~FLAG_STASHED_IN_TASKBAR_ALL_APPS
|
||||
& ~FLAG_STASHED_IN_APP_SYSUI;
|
||||
@@ -123,8 +129,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
*
|
||||
* Use {@link #getStashDuration()} to query duration
|
||||
*/
|
||||
private static final long TASKBAR_STASH_DURATION =
|
||||
InsetsController.ANIMATION_DURATION_RESIZE;
|
||||
private static final long TASKBAR_STASH_DURATION = InsetsController.ANIMATION_DURATION_RESIZE;
|
||||
|
||||
/**
|
||||
* How long to stash/unstash transient taskbar.
|
||||
@@ -146,8 +151,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
/**
|
||||
* How long the hint animation plays, starting on motion down.
|
||||
*/
|
||||
private static final long TASKBAR_HINT_STASH_DURATION =
|
||||
ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
|
||||
private static final long TASKBAR_HINT_STASH_DURATION = ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
|
||||
|
||||
/**
|
||||
* How long to delay the icon/stash handle alpha.
|
||||
@@ -160,17 +164,20 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
private static final long TASKBAR_STASH_ALPHA_DURATION = 50;
|
||||
|
||||
/**
|
||||
* How long to delay the icon/stash handle alpha for the home to app taskbar animation.
|
||||
* How long to delay the icon/stash handle alpha for the home to app taskbar
|
||||
* animation.
|
||||
*/
|
||||
private static final long TASKBAR_STASH_ICON_ALPHA_HOME_TO_APP_START_DELAY = 66;
|
||||
|
||||
/**
|
||||
* The scale that TaskbarView animates to when hinting towards the stashed state.
|
||||
* 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.
|
||||
* 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;
|
||||
|
||||
@@ -187,7 +194,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
// Auto stashes when user has not interacted with the Taskbar after X ms.
|
||||
private static final long NO_TOUCH_TIMEOUT_TO_STASH_MS = 5000;
|
||||
|
||||
// Duration for which an unlock event is considered "current", as other events are received
|
||||
// Duration for which an unlock event is considered "current", as other events
|
||||
// are received
|
||||
// asynchronously.
|
||||
private static final long UNLOCK_TRANSITION_MEMOIZATION_MS = 200;
|
||||
|
||||
@@ -196,22 +204,28 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
*/
|
||||
private static final int TRANSITION_DEFAULT = 0;
|
||||
/**
|
||||
* Transitioning from launcher to app. Same as TRANSITION_DEFAULT, differs in internal
|
||||
* Transitioning from launcher to app. Same as TRANSITION_DEFAULT, differs in
|
||||
* internal
|
||||
* animation timings.
|
||||
*/
|
||||
private static final int TRANSITION_HOME_TO_APP = 1;
|
||||
/**
|
||||
* Fading the navbar in and out, where the taskbar jumpcuts in and out at the very begin/end of
|
||||
* the transition. Used to transition between the hotseat and navbar` without the stash/unstash
|
||||
* Fading the navbar in and out, where the taskbar jumpcuts in and out at the
|
||||
* very begin/end of
|
||||
* the transition. Used to transition between the hotseat and navbar` without
|
||||
* the stash/unstash
|
||||
* transition.
|
||||
*/
|
||||
private static final int TRANSITION_HANDLE_FADE = 2;
|
||||
/**
|
||||
* Same as TRANSITION_DEFAULT, but exclusively used during an "navbar unstash to hotseat
|
||||
* animation" bound to the progress of a swipe gesture. It differs from TRANSITION_DEFAULT
|
||||
* Same as TRANSITION_DEFAULT, but exclusively used during an "navbar unstash to
|
||||
* hotseat
|
||||
* animation" bound to the progress of a swipe gesture. It differs from
|
||||
* TRANSITION_DEFAULT
|
||||
* by not scaling the height of the taskbar background.
|
||||
*/
|
||||
private static final int TRANSITION_UNSTASH_SUW_MANUAL = 3;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(value = {
|
||||
TRANSITION_DEFAULT,
|
||||
@@ -219,7 +233,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
TRANSITION_HANDLE_FADE,
|
||||
TRANSITION_UNSTASH_SUW_MANUAL,
|
||||
})
|
||||
private @interface StashAnimation {}
|
||||
private @interface StashAnimation {
|
||||
}
|
||||
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final SharedPreferences mPrefs;
|
||||
@@ -242,7 +257,10 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
private AnimatedFloat mTaskbarStashedHandleHintScale;
|
||||
private final AccessibilityManager mAccessibilityManager;
|
||||
|
||||
/** Whether we are currently visually stashed (might change based on launcher state). */
|
||||
/**
|
||||
* Whether we are currently visually stashed (might change based on launcher
|
||||
* state).
|
||||
*/
|
||||
private boolean mIsStashed = false;
|
||||
private int mState;
|
||||
|
||||
@@ -257,14 +275,15 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
private boolean mEnableBlockingTimeoutDuringTests = false;
|
||||
|
||||
// Evaluate whether the handle should be stashed
|
||||
private final IntPredicate mIsStashedPredicate = flags -> {
|
||||
boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP);
|
||||
boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP);
|
||||
boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE);
|
||||
boolean forceStashed = hasAnyFlag(flags, FLAGS_FORCE_STASHED);
|
||||
return (inApp && stashedInApp) || (!inApp && stashedLauncherState) || forceStashed;
|
||||
};
|
||||
private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
|
||||
flags -> {
|
||||
boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP);
|
||||
boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP);
|
||||
boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE);
|
||||
boolean forceStashed = hasAnyFlag(flags, FLAGS_FORCE_STASHED);
|
||||
return (inApp && stashedInApp) || (!inApp && stashedLauncherState) || forceStashed;
|
||||
});
|
||||
mIsStashedPredicate);
|
||||
|
||||
private boolean mIsTaskbarSystemActionRegistered = false;
|
||||
private TaskbarSharedState mTaskbarSharedState;
|
||||
@@ -312,16 +331,18 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
mIconScaleForStash = taskbarViewController.getTaskbarIconScaleForStash();
|
||||
mIconTranslationYForStash = taskbarViewController.getTaskbarIconTranslationYForStash();
|
||||
|
||||
StashedHandleViewController stashedHandleController =
|
||||
controllers.stashedHandleViewController;
|
||||
StashedHandleViewController stashedHandleController = controllers.stashedHandleViewController;
|
||||
mTaskbarStashedHandleAlpha = stashedHandleController.getStashedHandleAlpha().get(
|
||||
StashedHandleViewController.ALPHA_INDEX_STASHED);
|
||||
mTaskbarStashedHandleHintScale = stashedHandleController.getStashedHandleHintScale();
|
||||
|
||||
boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
|
||||
// We use supportsVisualStashing() here instead of supportsManualStashing() because we want
|
||||
// it to work properly for tests that recreate taskbar. This check is here just to ensure
|
||||
// that taskbar unstashes when going to 3 button mode (supportsVisualStashing() false).
|
||||
// We use supportsVisualStashing() here instead of supportsManualStashing()
|
||||
// because we want
|
||||
// it to work properly for tests that recreate taskbar. This check is here just
|
||||
// to ensure
|
||||
// that taskbar unstashes when going to 3 button mode (supportsVisualStashing()
|
||||
// false).
|
||||
boolean isManuallyStashedInApp = supportsVisualStashing()
|
||||
&& !isTransientTaskbar
|
||||
&& !ENABLE_TASKBAR_PINNING.get()
|
||||
@@ -333,16 +354,18 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
updateStateForFlag(FLAG_IN_SETUP, isInSetup);
|
||||
updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, isPhoneMode()
|
||||
&& !mActivity.isThreeButtonNav());
|
||||
// For now, assume we're in an app, since LauncherTaskbarUIController won't be able to tell
|
||||
// us that we're paused until a bit later. This avoids flickering upon recreating taskbar.
|
||||
// For now, assume we're in an app, since LauncherTaskbarUIController won't be
|
||||
// able to tell
|
||||
// us that we're paused until a bit later. This avoids flickering upon
|
||||
// recreating taskbar.
|
||||
updateStateForFlag(FLAG_IN_APP, true);
|
||||
applyState(/* duration = */ 0);
|
||||
|
||||
notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the taskbar can visually stash into a handle based on the current device
|
||||
* Returns whether the taskbar can visually stash into a handle based on the
|
||||
* current device
|
||||
* state.
|
||||
*/
|
||||
public boolean supportsVisualStashing() {
|
||||
@@ -350,7 +373,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the user can manually stash the taskbar based on the current device state.
|
||||
* Returns whether the user can manually stash the taskbar based on the current
|
||||
* device state.
|
||||
*/
|
||||
protected boolean supportsManualStashing() {
|
||||
if (ENABLE_TASKBAR_PINNING.get() && mPrefs.getBoolean(TASKBAR_PINNING_KEY, false)) {
|
||||
@@ -363,7 +387,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables support for manual stashing. This should only be used to add this functionality
|
||||
* Enables support for manual stashing. This should only be used to add this
|
||||
* functionality
|
||||
* to Launcher specific tests.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@@ -372,7 +397,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the auto timeout for taskbar stashing. This method should only be used for taskbar
|
||||
* Enables the auto timeout for taskbar stashing. This method should only be
|
||||
* used for taskbar
|
||||
* testing.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@@ -407,7 +433,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the taskbar should be stashed in apps (e.g. user long pressed to stash).
|
||||
* 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);
|
||||
@@ -435,9 +462,9 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
return (flags & flagMask) != 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the taskbar is currently visible and not in the process of being stashed.
|
||||
* Returns whether the taskbar is currently visible and not in the process of
|
||||
* being stashed.
|
||||
*/
|
||||
public boolean isTaskbarVisibleAndNotStashing() {
|
||||
return !mIsStashed && mControllers.taskbarViewController.areIconsVisible();
|
||||
@@ -458,6 +485,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
/**
|
||||
* Returns the height that taskbar will inset when inside apps.
|
||||
*
|
||||
* @see android.view.WindowInsets.Type#navigationBars()
|
||||
* @see android.view.WindowInsets.Type#systemBars()
|
||||
*/
|
||||
@@ -479,7 +507,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
if (!mControllers.stashedHandleViewController.isStashedHandleVisible()
|
||||
&& isInApp()
|
||||
&& !isAnimating) {
|
||||
// We are in a settled state where we're not showing the handle even though taskbar
|
||||
// We are in a settled state where we're not showing the handle even though
|
||||
// taskbar
|
||||
// is stashed. This can happen for example when home button is disabled (see
|
||||
// StashedHandleViewController#setIsHomeButtonDisabled()).
|
||||
return 0;
|
||||
@@ -492,6 +521,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
/**
|
||||
* Returns the height that taskbar will inset when inside apps.
|
||||
*
|
||||
* @see android.view.WindowInsets.Type#tappableElement()
|
||||
*/
|
||||
public int getTappableHeightToReportToApps() {
|
||||
@@ -504,19 +534,30 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
/**
|
||||
* Stash or unstashes the transient taskbar.
|
||||
* Stash or unstashes the transient taskbar, using the default
|
||||
* TASKBAR_STASH_DURATION.
|
||||
* If bubble bar exists, it will match taskbars stashing behavior.
|
||||
*/
|
||||
public void updateAndAnimateTransientTaskbar(boolean stash) {
|
||||
updateAndAnimateTransientTaskbar(stash, /* shouldBubblesFollow= */ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stash or unstashes the transient taskbar.
|
||||
*
|
||||
* @param stash whether transient taskbar should be stashed.
|
||||
* @param shouldBubblesFollow whether bubbles should match taskbars behavior.
|
||||
*/
|
||||
public void updateAndAnimateTransientTaskbar(boolean stash, boolean shouldBubblesFollow) {
|
||||
if (!DisplayController.isTransientTaskbar(mActivity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
stash
|
||||
if (stash
|
||||
&& !mControllers.taskbarAutohideSuspendController
|
||||
.isSuspendedForTransientTaskbarInLauncher()
|
||||
.isSuspendedForTransientTaskbarInLauncher()
|
||||
&& mControllers.taskbarAutohideSuspendController
|
||||
.isTransientTaskbarStashingSuspended()) {
|
||||
.isTransientTaskbarStashingSuspended()) {
|
||||
// Avoid stashing if autohide is currently suspended.
|
||||
return;
|
||||
}
|
||||
@@ -525,15 +566,45 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, stash);
|
||||
applyState();
|
||||
}
|
||||
|
||||
mControllers.bubbleControllers.ifPresent(controllers -> {
|
||||
if (shouldBubblesFollow) {
|
||||
final boolean willStash = mIsStashedPredicate.test(mState);
|
||||
if (willStash != controllers.bubbleStashController.isStashed()) {
|
||||
// Typically bubbles gets stashed / unstashed along with Taskbar, however, if
|
||||
// taskbar is becoming stashed because bubbles is being expanded, we don't want
|
||||
// to stash bubbles.
|
||||
if (willStash) {
|
||||
controllers.bubbleStashController.stashBubbleBar();
|
||||
} else {
|
||||
controllers.bubbleStashController.showBubbleBar(false /* expandBubbles */);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stashes transient taskbar after it has timed out.
|
||||
*/
|
||||
private void updateAndAnimateTransientTaskbarForTimeout() {
|
||||
// If bubbles are expanded we shouldn't stash them when taskbar is hidden
|
||||
// for the timeout.
|
||||
boolean bubbleBarExpanded = mControllers.bubbleControllers.isPresent()
|
||||
&& mControllers.bubbleControllers.get().bubbleBarViewController.isExpanded();
|
||||
updateAndAnimateTransientTaskbar(/* stash= */ true,
|
||||
/* shouldBubblesFollow= */ !bubbleBarExpanded);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
// 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;
|
||||
}
|
||||
@@ -548,18 +619,23 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether taskbar will unstash when long pressing it based on the current state. The
|
||||
* only time this is true is if the user is in an app and the taskbar is only stashed because
|
||||
* the user previously long pressed to manually stash (not due to other reasons like IME).
|
||||
* Returns whether taskbar will unstash when long pressing it based on the
|
||||
* current state. The
|
||||
* only time this is true is if the user is in an app and the taskbar is only
|
||||
* stashed because
|
||||
* the user previously long pressed to manually stash (not due to other reasons
|
||||
* like IME).
|
||||
*/
|
||||
private boolean canCurrentlyManuallyUnstash() {
|
||||
return (mState & (FLAG_IN_APP | FLAGS_STASHED_IN_APP))
|
||||
== (FLAG_IN_APP | FLAG_STASHED_IN_APP_MANUAL);
|
||||
return (mState & (FLAG_IN_APP | FLAGS_STASHED_IN_APP)) == (FLAG_IN_APP | FLAG_STASHED_IN_APP_MANUAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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()) {
|
||||
@@ -576,22 +652,28 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
/** Toggles the Taskbar's stash state. */
|
||||
public void toggleTaskbarStash() {
|
||||
if (!DisplayController.isTransientTaskbar(mActivity) || !hasAnyFlag(FLAGS_IN_APP)) return;
|
||||
if (!DisplayController.isTransientTaskbar(mActivity) || !hasAnyFlag(FLAGS_IN_APP))
|
||||
return;
|
||||
updateAndAnimateTransientTaskbar(!hasAnyFlag(FLAG_STASHED_IN_APP_AUTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the Taskbar unstash to Hotseat animator to the animator set.
|
||||
*
|
||||
* This should be used to run a Taskbar unstash to Hotseat animation whose progress matches a
|
||||
* This should be used to run a Taskbar unstash to Hotseat animation whose
|
||||
* progress matches a
|
||||
* swipe progress.
|
||||
*
|
||||
* @param placeholderDuration a placeholder duration to be used to ensure all full-length
|
||||
* sub-animations are properly coordinated. This duration should not
|
||||
* actually be used since this animation tracks a swipe progress.
|
||||
* @param placeholderDuration a placeholder duration to be used to ensure all
|
||||
* full-length
|
||||
* sub-animations are properly coordinated. This
|
||||
* duration should not
|
||||
* actually be used since this animation tracks a
|
||||
* swipe progress.
|
||||
*/
|
||||
protected void addUnstashToHotseatAnimation(AnimatorSet animation, int placeholderDuration) {
|
||||
// Defer any UI updates now to avoid the UI becoming stale when the animation plays.
|
||||
// Defer any UI updates now to avoid the UI becoming stale when the animation
|
||||
// plays.
|
||||
mControllers.taskbarViewController.setDeferUpdatesForSUW(true);
|
||||
createAnimToIsStashed(
|
||||
/* isStashed= */ false,
|
||||
@@ -604,8 +686,9 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
/**
|
||||
* 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 isStashed whether it's a stash animation or an unstash animation
|
||||
* @param duration duration of the animation
|
||||
* @param animationType what transition type to play.
|
||||
*/
|
||||
private void createAnimToIsStashed(boolean isStashed, long duration,
|
||||
@@ -632,11 +715,15 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
mAnimator.playTogether(mTaskbarBackgroundOffset.animateToValue(isStashed ? 1 : 0)
|
||||
.setDuration(duration));
|
||||
mAnimator.playTogether(mIconTranslationYForStash.animateToValue(isStashed
|
||||
? stashTranslation : 0)
|
||||
? stashTranslation
|
||||
: 0)
|
||||
.setDuration(duration));
|
||||
mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
|
||||
hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
|
||||
mAnimator.addListener(AnimatorListeners.forEndCallback(() -> mAnimator = null));
|
||||
mAnimator.addListener(AnimatorListeners.forEndCallback(() -> {
|
||||
mAnimator = null;
|
||||
mIsStashed = isStashed;
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -690,18 +777,15 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
firstHalfAnimatorSet.playTogether(
|
||||
mIconAlphaForStash.animateToValue(0),
|
||||
mIconScaleForStash.animateToValue(isPhoneMode() ?
|
||||
0 : STASHED_TASKBAR_SCALE)
|
||||
);
|
||||
mIconScaleForStash.animateToValue(isPhoneMode() ? 0 : STASHED_TASKBAR_SCALE));
|
||||
secondHalfAnimatorSet.playTogether(
|
||||
mTaskbarStashedHandleAlpha.animateToValue(1)
|
||||
);
|
||||
mTaskbarStashedHandleAlpha.animateToValue(1));
|
||||
|
||||
if (animationType == TRANSITION_HANDLE_FADE) {
|
||||
fullLengthAnimatorSet.setInterpolator(INSTANT);
|
||||
firstHalfAnimatorSet.setInterpolator(INSTANT);
|
||||
}
|
||||
} else {
|
||||
} else {
|
||||
firstHalfDurationScale = 0.5f;
|
||||
secondHalfDurationScale = 0.75f;
|
||||
|
||||
@@ -718,11 +802,9 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
firstHalfAnimatorSet.playTogether(
|
||||
mTaskbarStashedHandleAlpha.animateToValue(0)
|
||||
);
|
||||
mTaskbarStashedHandleAlpha.animateToValue(0));
|
||||
secondHalfAnimatorSet.playTogether(
|
||||
mIconAlphaForStash.animateToValue(1)
|
||||
);
|
||||
mIconAlphaForStash.animateToValue(1));
|
||||
|
||||
if (animationType == TRANSITION_HANDLE_FADE) {
|
||||
fullLengthAnimatorSet.setInterpolator(FINAL_FRAME);
|
||||
@@ -732,8 +814,10 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
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.
|
||||
// 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);
|
||||
@@ -789,7 +873,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
backgroundAndHandleAlphaStartDelay,
|
||||
backgroundAndHandleAlphaDuration, LINEAR);
|
||||
|
||||
// The rest of the animations might be "skipped" in TRANSITION_HANDLE_FADE transitions.
|
||||
// The rest of the animations might be "skipped" in TRANSITION_HANDLE_FADE
|
||||
// transitions.
|
||||
AnimatorSet skippable = as;
|
||||
if (animationType == TRANSITION_HANDLE_FADE) {
|
||||
skippable = new AnimatorSet();
|
||||
@@ -824,14 +909,19 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
play(skippable, mControllers.stashedHandleViewController
|
||||
.createRevealAnimToIsStashed(isStashed), 0, duration, EMPHASIZED);
|
||||
|
||||
// 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.
|
||||
// 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.
|
||||
skippable.play(mTaskbarStashedHandleHintScale.animateToValue(1f)
|
||||
.setDuration(isStashed ? duration / 2 : duration));
|
||||
}
|
||||
|
||||
private static void play(AnimatorSet as, Animator a, long startDelay, long duration,
|
||||
private static void play(AnimatorSet as, @Nullable Animator a, long startDelay, long duration,
|
||||
Interpolator interpolator) {
|
||||
if (a == null) {
|
||||
return;
|
||||
}
|
||||
a.setDuration(duration);
|
||||
a.setStartDelay(startDelay);
|
||||
a.setInterpolator(interpolator);
|
||||
@@ -840,8 +930,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
private void addJankMonitorListener(AnimatorSet animator, boolean expanding) {
|
||||
View v = mControllers.taskbarActivityContext.getDragLayer();
|
||||
int action = expanding ? InteractionJankMonitor.CUJ_TASKBAR_EXPAND :
|
||||
InteractionJankMonitor.CUJ_TASKBAR_COLLAPSE;
|
||||
int action = expanding ? InteractionJankMonitor.CUJ_TASKBAR_EXPAND
|
||||
: InteractionJankMonitor.CUJ_TASKBAR_COLLAPSE;
|
||||
animator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(@NonNull Animator animation) {
|
||||
@@ -854,10 +944,14 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and starts a partial stash animation, hinting at the new state that will trigger when
|
||||
* 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
|
||||
*
|
||||
* @param animateForward Whether we are going towards the new stashed state or
|
||||
* returning to the
|
||||
* unstashed state.
|
||||
*/
|
||||
public void startStashHint(boolean animateForward) {
|
||||
@@ -871,12 +965,14 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and starts a partial unstash animation, hinting at the new state that will trigger
|
||||
* 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
|
||||
* @param animateForward Whether we are going towards the new unstashed state or
|
||||
* returning to
|
||||
* the stashed state.
|
||||
* @param forceUnstash Whether we force the unstash hint to animate.
|
||||
* @param forceUnstash Whether we force the unstash hint to animate.
|
||||
*/
|
||||
protected void startUnstashHint(boolean animateForward, boolean forceUnstash) {
|
||||
if (!isStashed()) {
|
||||
@@ -921,7 +1017,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an animator which applies the latest state if mIsStashed is changed, or {@code null}
|
||||
* Returns an animator which applies the latest state if mIsStashed is changed,
|
||||
* or {@code null}
|
||||
* otherwise.
|
||||
*/
|
||||
@Nullable
|
||||
@@ -930,7 +1027,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when a system gesture starts and settles, so we can defer updating
|
||||
* 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) {
|
||||
@@ -951,7 +1049,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
/**
|
||||
* When hiding the IME, delay the unstash animation to align with the end of the transition.
|
||||
* When hiding the IME, delay the unstash animation to align with the end of the
|
||||
* transition.
|
||||
*/
|
||||
private long getTaskbarStashStartDelayForIme() {
|
||||
if (mIsImeShowing) {
|
||||
@@ -964,7 +1063,10 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
return imeExitDuration - TASKBAR_STASH_DURATION_FOR_IME;
|
||||
}
|
||||
|
||||
/** Called when some system ui state has changed. (See SYSUI_STATE_... in QuickstepContract) */
|
||||
/**
|
||||
* 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;
|
||||
@@ -993,9 +1095,9 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
/**
|
||||
* We stash when IME or IME switcher is showing AND NOT
|
||||
* * in small screen AND
|
||||
* * 3 button nav AND
|
||||
* * landscape (or seascape)
|
||||
* * in small screen AND
|
||||
* * 3 button nav AND
|
||||
* * landscape (or seascape)
|
||||
* We do not stash if taskbar is transient
|
||||
*/
|
||||
private boolean shouldStashForIme() {
|
||||
@@ -1010,10 +1112,12 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
/**
|
||||
* 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.
|
||||
* 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 /
|
||||
* @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) {
|
||||
@@ -1026,6 +1130,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
/**
|
||||
* Called after updateStateForFlag() and applyState() have been called.
|
||||
*
|
||||
* @param changedFlags The flags that have changed.
|
||||
*/
|
||||
private void onStateChangeApplied(int changedFlags) {
|
||||
@@ -1034,7 +1139,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
if (hasAnyFlag(changedFlags, FLAGS_STASHED_IN_APP | FLAGS_IN_APP)) {
|
||||
notifyStashChange(/* visible */ hasAnyFlag(FLAGS_IN_APP),
|
||||
/* stashed */ isStashedInApp());
|
||||
/* stashed */ isStashedInApp());
|
||||
mControllers.taskbarAutohideSuspendController.updateFlag(
|
||||
TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER, !isInApp());
|
||||
}
|
||||
@@ -1053,6 +1158,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR,
|
||||
!hasAnyFlag(FLAG_STASHED_IN_APP_AUTO));
|
||||
}
|
||||
mActivity.applyForciblyShownFlagWhileTransientTaskbarUnstashed(!isStashedInApp());
|
||||
}
|
||||
|
||||
private void notifyStashChange(boolean visible, boolean stashed) {
|
||||
@@ -1105,6 +1211,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
/**
|
||||
* Updates the status of the taskbar timeout.
|
||||
*
|
||||
* @param isAutohideSuspended If true, cancels any existing timeout
|
||||
* If false, attempts to re/start the timeout
|
||||
*/
|
||||
@@ -1136,7 +1243,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
/**
|
||||
* returns appropriate timeout for taskbar to stash depending on accessibility being on/off.
|
||||
* returns appropriate timeout for taskbar to stash depending on accessibility
|
||||
* being on/off.
|
||||
*/
|
||||
private long getTaskbarAutoHideTimeout() {
|
||||
return mAccessibilityManager.getRecommendedTimeoutMillis((int) NO_TOUCH_TIMEOUT_TO_STASH_MS,
|
||||
@@ -1147,7 +1255,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
if (mControllers.taskbarAutohideSuspendController.isTransientTaskbarStashingSuspended()) {
|
||||
return;
|
||||
}
|
||||
updateAndAnimateTransientTaskbar(true);
|
||||
updateAndAnimateTransientTaskbarForTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1194,9 +1302,11 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an animator (stored in mAnimator) which applies the latest state, potentially
|
||||
* Creates an animator (stored in mAnimator) which applies the latest state,
|
||||
* potentially
|
||||
* creating a new animation (stored in mAnimator).
|
||||
* @param flags The latest flags to apply (see the top of this file).
|
||||
*
|
||||
* @param flags The latest flags to apply (see the top of this file).
|
||||
* @param duration The length of the animation.
|
||||
* @return mAnimator if mIsStashed changed, or {@code null} otherwise.
|
||||
*/
|
||||
@@ -1224,15 +1334,17 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
if (isUnlockTransition) {
|
||||
// the launcher might not be resumed at the time the device is considered
|
||||
// unlocked (when the keyguard goes away), but possibly shortly afterwards.
|
||||
// To play the unlock transition at the time the unstash animation actually happens,
|
||||
// To play the unlock transition at the time the unstash animation actually
|
||||
// happens,
|
||||
// this memoizes the state transition for UNLOCK_TRANSITION_MEMOIZATION_MS.
|
||||
mLastUnlockTransitionTimeout =
|
||||
SystemClock.elapsedRealtime() + UNLOCK_TRANSITION_MEMOIZATION_MS;
|
||||
mLastUnlockTransitionTimeout = SystemClock.elapsedRealtime() + UNLOCK_TRANSITION_MEMOIZATION_MS;
|
||||
}
|
||||
|
||||
@StashAnimation int animationType = computeTransitionType(changedFlags);
|
||||
@StashAnimation
|
||||
int animationType = computeTransitionType(changedFlags);
|
||||
|
||||
// Allow re-starting animation if upgrading from default animation type, otherwise
|
||||
// Allow re-starting animation if upgrading from default animation type,
|
||||
// otherwise
|
||||
// stick with the already started transition.
|
||||
boolean transitionTypeChanged = mAnimator != null && mAnimator.isStarted()
|
||||
&& mLastStartedTransitionType == TRANSITION_DEFAULT
|
||||
@@ -1260,21 +1372,21 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
private @StashAnimation int computeTransitionType(int changedFlags) {
|
||||
|
||||
boolean hotseatHiddenDuringAppLaunch =
|
||||
!mControllers.uiController.isHotseatIconOnTopWhenAligned()
|
||||
&& hasAnyFlag(changedFlags, FLAG_IN_APP);
|
||||
boolean hotseatHiddenDuringAppLaunch = !mControllers.uiController.isHotseatIconOnTopWhenAligned()
|
||||
&& hasAnyFlag(changedFlags, FLAG_IN_APP);
|
||||
if (hotseatHiddenDuringAppLaunch) {
|
||||
// When launching an app from the all-apps drawer, the hotseat is hidden behind the
|
||||
// drawer. In this case, the navbar must just fade in, without a stash transition,
|
||||
// When launching an app from the all-apps drawer, the hotseat is hidden behind
|
||||
// the
|
||||
// drawer. In this case, the navbar must just fade in, without a stash
|
||||
// transition,
|
||||
// as the taskbar stash animation would otherwise be visible above the all-apps
|
||||
// drawer once the hotseat is detached.
|
||||
return TRANSITION_HANDLE_FADE;
|
||||
}
|
||||
|
||||
boolean isUnlockTransition =
|
||||
SystemClock.elapsedRealtime() < mLastUnlockTransitionTimeout;
|
||||
boolean isUnlockTransition = SystemClock.elapsedRealtime() < mLastUnlockTransitionTimeout;
|
||||
if (isUnlockTransition) {
|
||||
// When transitioning to unlocked device, the hotseat will already be visible on
|
||||
// When transitioning to unlocked device, the hotseat will already be visible on
|
||||
// the homescreen, thus do not play an un-stash animation.
|
||||
// Keep isUnlockTransition in sync with its counterpart in
|
||||
// TaskbarLauncherStateController#onStateChangeApplied.
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
package com.android.launcher3.taskbar
|
||||
|
||||
import android.view.MotionEvent
|
||||
import com.android.app.animation.Interpolators.LINEAR
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.Utilities
|
||||
import com.android.launcher3.anim.Interpolators.LINEAR
|
||||
import com.android.launcher3.testing.shared.ResourceUtils
|
||||
import com.android.launcher3.touch.SingleAxisSwipeDetector
|
||||
import com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE
|
||||
@@ -108,7 +108,17 @@ class TaskbarStashViaTouchController(val controllers: TaskbarControllers) : Touc
|
||||
}
|
||||
|
||||
override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||
if (!enabled || controllers.taskbarStashController.isStashed) {
|
||||
if (!enabled) {
|
||||
return false
|
||||
}
|
||||
val bubbleControllers = controllers.bubbleControllers.orElse(null)
|
||||
if (bubbleControllers != null && bubbleControllers.bubbleBarViewController.isExpanded) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
(bubbleControllers == null || bubbleControllers.bubbleStashController.isStashed) &&
|
||||
controllers.taskbarStashController.isStashed
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -122,7 +132,12 @@ class TaskbarStashViaTouchController(val controllers: TaskbarControllers) : Touc
|
||||
return true
|
||||
}
|
||||
} else if (ev.action == MotionEvent.ACTION_DOWN) {
|
||||
if (screenCoordinatesEv.y < gestureHeightYThreshold) {
|
||||
val isDownOnBubbleBar =
|
||||
(bubbleControllers != null &&
|
||||
bubbleControllers.bubbleBarViewController.isEventOverAnyItem(
|
||||
screenCoordinatesEv
|
||||
))
|
||||
if (!isDownOnBubbleBar && screenCoordinatesEv.y < gestureHeightYThreshold) {
|
||||
controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.Utilities.dpToPx;
|
||||
import static com.android.launcher3.Utilities.dpiFromPx;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
|
||||
/**
|
||||
* Utility class that contains the different taskbar thresholds logic.
|
||||
*/
|
||||
public class TaskbarThresholdUtils {
|
||||
|
||||
// We divide the screen into this many parts, and use the result to scale the thresholds to
|
||||
// any size device. Note that this value was calculated arbitrarily by using two tablet devices
|
||||
// as data points.
|
||||
private static final float SCREEN_UNITS = 1 / 80f;
|
||||
|
||||
private static int getThreshold(Resources r, DeviceProfile dp, int thresholdDimen,
|
||||
int multiplierDimen) {
|
||||
if (!FeatureFlags.ENABLE_DYNAMIC_TASKBAR_THRESHOLDS.get()) {
|
||||
return r.getDimensionPixelSize(thresholdDimen);
|
||||
}
|
||||
|
||||
float landscapeScreenHeight = dp.isLandscape ? dp.heightPx : dp.widthPx;
|
||||
float screenPart = (landscapeScreenHeight * SCREEN_UNITS);
|
||||
float defaultDp = dpiFromPx(screenPart, DisplayMetrics.DENSITY_DEVICE_STABLE);
|
||||
float thisDp = dpToPx(defaultDp);
|
||||
float multiplier = ResourcesCompat.getFloat(r, multiplierDimen);
|
||||
float value = (thisDp) * multiplier;
|
||||
|
||||
return Math.round(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the threshold that determines if we should show taskbar.
|
||||
*/
|
||||
public static int getFromNavThreshold(Resources r, DeviceProfile dp) {
|
||||
return getThreshold(r, dp, R.dimen.taskbar_from_nav_threshold,
|
||||
R.dimen.taskbar_nav_threshold_mult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the threshold that we start moving the app window.
|
||||
*/
|
||||
public static int getAppWindowThreshold(Resources r, DeviceProfile dp) {
|
||||
return getThreshold(r, dp, R.dimen.taskbar_app_window_threshold,
|
||||
R.dimen.taskbar_app_window_threshold_mult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the threshold for whether we land in home or overview.
|
||||
*/
|
||||
public static int getHomeOverviewThreshold(Resources r, DeviceProfile dp) {
|
||||
return getThreshold(r, dp, R.dimen.taskbar_home_overview_threshold,
|
||||
R.dimen.taskbar_home_overview_threshold_mult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the threshold that we use to allow swipe to catch up to finger.
|
||||
*/
|
||||
public static int getCatchUpThreshold(Resources r, DeviceProfile dp) {
|
||||
return getThreshold(r, dp, R.dimen.taskbar_catch_up_threshold,
|
||||
R.dimen.taskbar_catch_up_threshold_mult);
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,8 @@ import android.animation.ValueAnimator;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.anim.SpringAnimationBuilder;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
|
||||
@@ -92,6 +92,10 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
|
||||
mControllers.stashedHandleViewController.setTranslationYForSwipe(transY);
|
||||
mControllers.taskbarViewController.setTranslationYForSwipe(transY);
|
||||
mControllers.taskbarDragLayerController.setTranslationYForSwipe(transY);
|
||||
mControllers.bubbleControllers.ifPresent(controllers -> {
|
||||
controllers.bubbleBarViewController.setTranslationYForSwipe(transY);
|
||||
controllers.bubbleStashedHandleViewController.setTranslationYForSwipe(transY);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,8 +40,11 @@ import com.android.quickstep.util.GroupTask;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
@@ -69,26 +72,32 @@ public class TaskbarUIController {
|
||||
}
|
||||
|
||||
/**
|
||||
* This should only be called by TaskbarStashController so that a TaskbarUIController can
|
||||
* This should only be called by TaskbarStashController so that a
|
||||
* TaskbarUIController can
|
||||
* disable stashing. All other controllers should use
|
||||
* {@link TaskbarStashController#supportsVisualStashing()} as the source of truth.
|
||||
* {@link TaskbarStashController#supportsVisualStashing()} as the source of
|
||||
* truth.
|
||||
*/
|
||||
public boolean supportsVisualStashing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void onStashedInAppChanged() { }
|
||||
protected void onStashedInAppChanged() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when taskbar icon layout bounds change.
|
||||
*/
|
||||
protected void onIconLayoutBoundsChanged() { }
|
||||
protected void onIconLayoutBoundsChanged() {
|
||||
}
|
||||
|
||||
/** Called when an icon is launched. */
|
||||
@CallSuper
|
||||
public void onTaskbarIconLaunched(ItemInfo item) {
|
||||
// When launching from Taskbar, e.g. from Overview, set FLAG_IN_APP immediately instead of
|
||||
// waiting for onPause, to reduce potential visual noise during the app open transition.
|
||||
// When launching from Taskbar, e.g. from Overview, set FLAG_IN_APP immediately
|
||||
// instead of
|
||||
// waiting for onPause, to reduce potential visual noise during the app open
|
||||
// transition.
|
||||
mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP, true);
|
||||
mControllers.taskbarStashController.applyState();
|
||||
}
|
||||
@@ -99,7 +108,9 @@ public class TaskbarUIController {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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);
|
||||
@@ -116,7 +127,8 @@ public class TaskbarUIController {
|
||||
}
|
||||
|
||||
/**
|
||||
* User expands PiP to full-screen (or split-screen) mode, try to hide the Taskbar.
|
||||
* User expands PiP to full-screen (or split-screen) mode, try to hide the
|
||||
* Taskbar.
|
||||
*/
|
||||
public void onExpandPip() {
|
||||
if (mControllers != null) {
|
||||
@@ -155,7 +167,9 @@ public class TaskbarUIController {
|
||||
|
||||
/*
|
||||
* @param ev MotionEvent in screen coordinates.
|
||||
* @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
|
||||
*
|
||||
* @return Whether any Taskbar item could handle the given MotionEvent if given
|
||||
* the chance.
|
||||
*/
|
||||
public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
|
||||
return mControllers.taskbarViewController.isEventOverAnyItem(ev)
|
||||
@@ -170,7 +184,8 @@ public class TaskbarUIController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if hotseat icons are on top of view hierarchy when aligned in the current state.
|
||||
* Returns true if hotseat icons are on top of view hierarchy when aligned in
|
||||
* the current state.
|
||||
*/
|
||||
public boolean isHotseatIconOnTopWhenAligned() {
|
||||
return true;
|
||||
@@ -191,8 +206,10 @@ public class TaskbarUIController {
|
||||
|
||||
/**
|
||||
* Returns RecentsView. Overwritten in LauncherTaskbarUIController and
|
||||
* FallbackTaskbarUIController with Launcher-specific implementations. Returns null for other
|
||||
* UI controllers (like DesktopTaskbarUIController) that don't have a RecentsView.
|
||||
* FallbackTaskbarUIController with Launcher-specific implementations. Returns
|
||||
* null for other
|
||||
* UI controllers (like DesktopTaskbarUIController) that don't have a
|
||||
* RecentsView.
|
||||
*/
|
||||
public @Nullable RecentsView getRecentsView() {
|
||||
return null;
|
||||
@@ -204,16 +221,17 @@ public class TaskbarUIController {
|
||||
return;
|
||||
}
|
||||
|
||||
recentsView.getSplitSelectController().findLastActiveTaskAndRunCallback(
|
||||
splitSelectSource.itemInfo.getComponentKey(),
|
||||
foundTask -> {
|
||||
recentsView.getSplitSelectController().findLastActiveTasksAndRunCallback(
|
||||
Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()),
|
||||
foundTasks -> {
|
||||
@Nullable
|
||||
Task foundTask = foundTasks.get(0);
|
||||
splitSelectSource.alreadyRunningTaskId = foundTask == null
|
||||
? INVALID_TASK_ID
|
||||
: foundTask.key.id;
|
||||
splitSelectSource.animateCurrentTaskDismissal = foundTask != null;
|
||||
recentsView.initiateSplitSelect(splitSelectSource);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,9 +239,11 @@ public class TaskbarUIController {
|
||||
*/
|
||||
public void triggerSecondAppForSplit(ItemInfoWithIcon info, Intent intent, View startingView) {
|
||||
RecentsView recents = getRecentsView();
|
||||
recents.getSplitSelectController().findLastActiveTaskAndRunCallback(
|
||||
info.getComponentKey(),
|
||||
foundTask -> {
|
||||
recents.getSplitSelectController().findLastActiveTasksAndRunCallback(
|
||||
Collections.singletonList(info.getComponentKey()),
|
||||
foundTasks -> {
|
||||
@Nullable
|
||||
Task foundTask = foundTasks.get(0);
|
||||
if (foundTask != null) {
|
||||
TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
|
||||
// TODO (b/266482558): This additional null check is needed because there
|
||||
@@ -234,8 +254,8 @@ public class TaskbarUIController {
|
||||
if (foundTaskView != null) {
|
||||
// There is already a running app of this type, use that as second app.
|
||||
// Get index of task (0 or 1), in case it's a GroupedTaskView
|
||||
TaskIdAttributeContainer taskAttributes =
|
||||
foundTaskView.getTaskAttributesById(foundTask.key.id);
|
||||
TaskIdAttributeContainer taskAttributes = foundTaskView
|
||||
.getTaskAttributesById(foundTask.key.id);
|
||||
recents.confirmSplitSelect(
|
||||
foundTaskView,
|
||||
foundTask,
|
||||
@@ -257,14 +277,14 @@ public class TaskbarUIController {
|
||||
null /* thumbnail */,
|
||||
intent,
|
||||
info.user);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the Keyboard Quick Switch View.
|
||||
*
|
||||
* This will set the focus to the first task from the right (from the left in RTL)
|
||||
* This will set the focus to the first task from the right (from the left in
|
||||
* RTL)
|
||||
*/
|
||||
public void openQuickSwitchView() {
|
||||
mControllers.keyboardQuickSwitchController.openQuickSwitchView();
|
||||
@@ -273,10 +293,13 @@ public class TaskbarUIController {
|
||||
/**
|
||||
* Launches the focused task and closes the Keyboard Quick Switch View.
|
||||
*
|
||||
* If the overlay or view are closed, or the overview task is focused, then Overview is
|
||||
* launched. If the overview task is launched, then the first hidden task is focused.
|
||||
* If the overlay or view are closed, or the overview task is focused, then
|
||||
* Overview is
|
||||
* launched. If the overview task is launched, then the first hidden task is
|
||||
* focused.
|
||||
*
|
||||
* @return the index of what task should be focused in ; -1 iff Overview shouldn't be launched
|
||||
* @return the index of what task should be focused in ; -1 iff Overview
|
||||
* shouldn't be launched
|
||||
*/
|
||||
public int launchFocusedTask() {
|
||||
int focusedTaskIndex = mControllers.keyboardQuickSwitchController.launchFocusedTask();
|
||||
@@ -289,10 +312,12 @@ public class TaskbarUIController {
|
||||
*
|
||||
* No-op if the view is not yet open.
|
||||
*/
|
||||
public void launchSplitTasks(@NonNull View taskview, @NonNull GroupTask groupTask) { }
|
||||
public void launchSplitTasks(@NonNull View taskview, @NonNull GroupTask groupTask) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the matching view (if any) in the taskbar.
|
||||
*
|
||||
* @param view The view to match.
|
||||
*/
|
||||
public @Nullable View findMatchingView(View view) {
|
||||
@@ -304,7 +329,8 @@ public class TaskbarUIController {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Taskbar has the same items as the hotseat and we can use screenId to find the match.
|
||||
// Taskbar has the same items as the hotseat and we can use screenId to find the
|
||||
// match.
|
||||
int screenId = info.screenId;
|
||||
View[] views = mControllers.taskbarViewController.getIconViews();
|
||||
for (int i = views.length - 1; i >= 0; --i) {
|
||||
@@ -320,7 +346,8 @@ public class TaskbarUIController {
|
||||
/**
|
||||
* Refreshes the resumed state of this ui controller.
|
||||
*/
|
||||
public void refreshResumedState() {}
|
||||
public void refreshResumedState() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream of split screen menu options appropriate to the device.
|
||||
|
||||
@@ -47,10 +47,9 @@ public class TaskbarUnfoldAnimationController implements
|
||||
WindowManager windowManager,
|
||||
RotationChangeProvider rotationChangeProvider) {
|
||||
mScopedUnfoldTransitionProgressProvider = source;
|
||||
mNaturalUnfoldTransitionProgressProvider =
|
||||
new NaturalRotationUnfoldProgressProvider(context,
|
||||
rotationChangeProvider,
|
||||
source);
|
||||
mNaturalUnfoldTransitionProgressProvider = new NaturalRotationUnfoldProgressProvider(context,
|
||||
rotationChangeProvider,
|
||||
source);
|
||||
mMoveFromCenterAnimator = new UnfoldMoveFromCenterAnimator(windowManager,
|
||||
new LauncherViewsMoveFromCenterTranslationApplier());
|
||||
}
|
||||
@@ -63,8 +62,8 @@ public class TaskbarUnfoldAnimationController implements
|
||||
public void init(TaskbarControllers taskbarControllers) {
|
||||
mNaturalUnfoldTransitionProgressProvider.init();
|
||||
mTaskbarViewController = taskbarControllers.taskbarViewController;
|
||||
mTaskbarViewController.addOneTimePreDrawListener(() ->
|
||||
mScopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(true));
|
||||
mTaskbarViewController.addOneTimePreDrawListener(
|
||||
() -> mScopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(true));
|
||||
mNaturalUnfoldTransitionProgressProvider.addCallback(mTransitionListener);
|
||||
mTaskbarDragLayerController = taskbarControllers.taskbarDragLayerController;
|
||||
}
|
||||
@@ -91,8 +90,9 @@ public class TaskbarUnfoldAnimationController implements
|
||||
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
|
||||
// TODO(b/193794563) we should re-register views if they are
|
||||
// re-bound/re-inflated
|
||||
// during the animation
|
||||
mMoveFromCenterAnimator.registerViewForAnimation(icon);
|
||||
}
|
||||
|
||||
@@ -112,9 +112,5 @@ public class TaskbarUnfoldAnimationController implements
|
||||
float insetPercentage = (1 - progress) * MAX_WIDTH_INSET_FRACTION;
|
||||
mTaskbarDragLayerController.setBackgroundHorizontalInsets(insetPercentage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionFinishing() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.launcher3.taskbar;
|
||||
import static android.content.pm.PackageManager.FEATURE_PC;
|
||||
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
|
||||
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_CURSOR_HOVER_STATES;
|
||||
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -61,7 +62,8 @@ import app.lawnchair.hotseat.HotseatMode;
|
||||
import app.lawnchair.preferences2.PreferenceManager2;
|
||||
|
||||
/**
|
||||
* Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
|
||||
* Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of
|
||||
* other apps.
|
||||
*/
|
||||
public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable,
|
||||
DeviceProfile.OnDeviceProfileChangeListener {
|
||||
@@ -91,7 +93,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
private @Nullable IconButtonView mAllAppsButton;
|
||||
|
||||
// Only non-null when device supports having an All Apps button.
|
||||
private @Nullable View mTaskbarDivider;
|
||||
private @Nullable IconButtonView mTaskbarDivider;
|
||||
|
||||
private View mQsb;
|
||||
|
||||
@@ -110,12 +112,12 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
}
|
||||
|
||||
public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs,
|
||||
int defStyleAttr) {
|
||||
int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
PreferenceManager2 preferenceManager2 = PreferenceManager2.getInstance(context);
|
||||
HotseatMode hotseatMode = PreferenceExtensionsKt.firstBlocking(preferenceManager2.getHotseatMode());
|
||||
@@ -127,10 +129,9 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
mIsRtl = Utilities.isRtl(resources);
|
||||
mTransientTaskbarMinWidth = context.getResources().getDimension(
|
||||
R.dimen.transient_taskbar_min_width);
|
||||
mTransientTaskbarAllAppsButtonTranslationXOffset =
|
||||
resources.getDimension(isTransientTaskbar
|
||||
? R.dimen.transient_taskbar_all_apps_button_translation_x_offset
|
||||
: R.dimen.taskbar_all_apps_button_translation_x_offset);
|
||||
mTransientTaskbarAllAppsButtonTranslationXOffset = resources.getDimension(isTransientTaskbar
|
||||
? R.dimen.transient_taskbar_all_apps_button_translation_x_offset
|
||||
: R.dimen.taskbar_all_apps_button_translation_x_offset);
|
||||
|
||||
onDeviceProfileChanged(mActivityContext.getDeviceProfile());
|
||||
|
||||
@@ -163,13 +164,17 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
mActivityContext.getColor(R.color.all_apps_button_color));
|
||||
|
||||
if (FeatureFlags.ENABLE_TASKBAR_PINNING.get()) {
|
||||
mTaskbarDivider = LayoutInflater.from(context).inflate(R.layout.taskbar_divider,
|
||||
mTaskbarDivider = (IconButtonView) LayoutInflater.from(context).inflate(
|
||||
R.layout.taskbar_divider,
|
||||
this, false);
|
||||
mTaskbarDivider.setIconDrawable(
|
||||
resources.getDrawable(R.drawable.taskbar_divider_button));
|
||||
mTaskbarDivider.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Disable touch events on QSB otherwise it can crash.
|
||||
if (hotseatMode.isAvailable (context)) {
|
||||
if (hotseatMode.isAvailable(context)) {
|
||||
mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
|
||||
} else {
|
||||
mQsb = LayoutInflater.from(context).inflate(R.layout.empty_view, this, false);
|
||||
@@ -207,7 +212,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
protected void announceAccessibilityChanges() {
|
||||
this.performAccessibilityAction(
|
||||
isVisibleToUser() ? AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS
|
||||
: AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
|
||||
: AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
|
||||
null);
|
||||
|
||||
ActivityContext.lookupContext(getContext()).getDragLayer()
|
||||
.sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
|
||||
@@ -221,7 +227,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
}
|
||||
|
||||
protected void init(TaskbarViewController.TaskbarViewCallbacks callbacks) {
|
||||
// set taskbar pane title so that accessibility service know it window and focuses.
|
||||
// set taskbar pane title so that accessibility service know it window and
|
||||
// focuses.
|
||||
setAccessibilityPaneTitle(getContext().getString(R.string.taskbar_a11y_title));
|
||||
mControllerCallbacks = callbacks;
|
||||
mIconClickListener = mControllerCallbacks.getIconOnClickListener();
|
||||
@@ -249,7 +256,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates/binds the Hotseat views to show in the Taskbar given their ItemInfos.
|
||||
* Inflates/binds the Hotseat views to show in the Taskbar given their
|
||||
* ItemInfos.
|
||||
*/
|
||||
protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
|
||||
int nextViewIndex = 0;
|
||||
@@ -264,14 +272,14 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
}
|
||||
removeView(mQsb);
|
||||
|
||||
|
||||
for (int i = 0; i < hotseatItemInfos.length; i++) {
|
||||
ItemInfo hotseatItemInfo = hotseatItemInfos[i];
|
||||
if (hotseatItemInfo == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Replace any Hotseat views with the appropriate type if it's not already that type.
|
||||
// Replace any Hotseat views with the appropriate type if it's not already that
|
||||
// type.
|
||||
final int expectedLayoutResId;
|
||||
boolean isFolder = false;
|
||||
if (hotseatItemInfo.isPredictedItem()) {
|
||||
@@ -316,7 +324,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
addView(hotseatView, nextViewIndex, lp);
|
||||
}
|
||||
|
||||
// Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
|
||||
// Apply the Hotseat ItemInfos, or hide the view if there is none for a given
|
||||
// index.
|
||||
if (hotseatView instanceof BubbleTextView
|
||||
&& hotseatItemInfo instanceof WorkspaceItemInfo) {
|
||||
BubbleTextView btv = (BubbleTextView) hotseatView;
|
||||
@@ -329,6 +338,9 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
}
|
||||
}
|
||||
setClickAndLongClickListenersForIcon(hotseatView);
|
||||
if (ENABLE_CURSOR_HOVER_STATES.get()) {
|
||||
setHoverListenerForIcon(hotseatView);
|
||||
}
|
||||
nextViewIndex++;
|
||||
}
|
||||
// Remove remaining views
|
||||
@@ -338,7 +350,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
|
||||
if (mAllAppsButton != null) {
|
||||
mAllAppsButton.setTranslationXForTaskbarAllAppsIcon(getChildCount() > 0
|
||||
? mTransientTaskbarAllAppsButtonTranslationXOffset : 0f);
|
||||
? mTransientTaskbarAllAppsButtonTranslationXOffset
|
||||
: 0f);
|
||||
addView(mAllAppsButton, mIsRtl ? getChildCount() : 0);
|
||||
|
||||
// if only all apps button present, don't include divider view.
|
||||
@@ -376,16 +389,18 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
icon.setOnLongClickListener(mIconLongClickListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets OnHoverListener for the given view.
|
||||
*/
|
||||
private void setHoverListenerForIcon(View icon) {
|
||||
icon.setOnHoverListener(mControllerCallbacks.getIconOnHoverListener(icon));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
int count = getChildCount();
|
||||
DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
|
||||
int spaceNeeded = getIconLayoutWidth();
|
||||
// We are removing the margin from taskbar divider item in taskbar,
|
||||
// so remove it from spacing also.
|
||||
if (FeatureFlags.ENABLE_TASKBAR_PINNING.get() && count > 1) {
|
||||
spaceNeeded -= mIconTouchSize;
|
||||
}
|
||||
int navSpaceNeeded = deviceProfile.hotseatBarEndOffset;
|
||||
boolean layoutRtl = isLayoutRtl();
|
||||
int centerAlignIconEnd = right - (right - left - spaceNeeded) / 2;
|
||||
@@ -483,7 +498,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's
|
||||
* Returns whether the given MotionEvent, *in screen coorindates*, is within any
|
||||
* Taskbar item's
|
||||
* touch bounds.
|
||||
*/
|
||||
public boolean isEventOverAnyItem(MotionEvent ev) {
|
||||
@@ -506,7 +522,14 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
if (deviceProfile.isQsbInline) {
|
||||
countExcludingQsb--;
|
||||
}
|
||||
return countExcludingQsb * (mItemMarginLeftRight * 2 + mIconTouchSize);
|
||||
int iconLayoutBoundsWidth = countExcludingQsb * (mItemMarginLeftRight * 2 + mIconTouchSize);
|
||||
|
||||
if (FeatureFlags.ENABLE_TASKBAR_PINNING.get() && countExcludingQsb > 1) {
|
||||
// We are removing 4 * mItemMarginLeftRight as there should be no space between
|
||||
// All Apps icon, divider icon, and first app icon in taskbar
|
||||
iconLayoutBoundsWidth -= mItemMarginLeftRight * 4;
|
||||
}
|
||||
return iconLayoutBoundsWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -600,7 +623,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first icon to match one of the given matchers, from highest to lowest priority.
|
||||
* Finds the first icon to match one of the given matchers, from highest to
|
||||
* lowest priority.
|
||||
*
|
||||
* @return The first match, or All Apps button if no match was found.
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.app.animation.Interpolators.FINAL_FRAME;
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
|
||||
@@ -22,9 +24,8 @@ import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
|
||||
import static com.android.launcher3.Utilities.squaredHypot;
|
||||
import static com.android.launcher3.anim.AnimatedFloat.VALUE;
|
||||
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
|
||||
import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
|
||||
import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
|
||||
import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
|
||||
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
|
||||
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM;
|
||||
@@ -45,6 +46,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.core.view.OneShotPreDrawListener;
|
||||
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
@@ -53,7 +55,6 @@ import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AlphaUpdateListener;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.anim.RevealOutlineAnimation;
|
||||
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
|
||||
@@ -121,7 +122,9 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
private AnimatorPlaybackController mIconAlignControllerLazy = null;
|
||||
private Runnable mOnControllerPreCreateCallback = NO_OP;
|
||||
|
||||
// Stored here as signals to determine if the mIconAlignController needs to be recreated.
|
||||
private boolean mIsHotseatIconOnTopWhenAligned;
|
||||
private boolean mIsStashed;
|
||||
|
||||
private final DeviceProfile.OnDeviceProfileChangeListener mDeviceProfileChangeListener =
|
||||
dp -> commitRunningAppsToUI();
|
||||
@@ -133,7 +136,8 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
mTaskbarView = taskbarView;
|
||||
mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, NUM_ALPHA_CHANNELS);
|
||||
mTaskbarIconAlpha.setUpdateVisibility(true);
|
||||
mModelCallbacks = new TaskbarModelCallbacks(activity, mTaskbarView);
|
||||
mModelCallbacks = TaskbarModelCallbacksFactory.newInstance(mActivity)
|
||||
.create(mActivity, mTaskbarView);
|
||||
mTaskbarBottomMargin = activity.getDeviceProfile().taskbarBottomMargin;
|
||||
mStashedHandleHeight = activity.getResources()
|
||||
.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height);
|
||||
@@ -170,6 +174,13 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
.getTaskbarNavButtonTranslationYForInAppDisplay();
|
||||
|
||||
mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
|
||||
|
||||
if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
|
||||
// This gets modified in NavbarButtonsViewController, but the initial value it reads
|
||||
// may be incorrect since it's state gets destroyed on taskbar recreate, so reset here
|
||||
mTaskbarIconAlpha.get(ALPHA_INDEX_SMALL_SCREEN)
|
||||
.animateToValue(isPhoneButtonNavMode(mActivity) ? 0 : 1).start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -426,10 +437,13 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
|
||||
boolean isHotseatIconOnTopWhenAligned =
|
||||
mControllers.uiController.isHotseatIconOnTopWhenAligned();
|
||||
// When mIsHotseatIconOnTopWhenAligned changes, animation needs to be re-created.
|
||||
boolean isStashed = mControllers.taskbarStashController.isStashed();
|
||||
// Re-create animation when mIsHotseatIconOnTopWhenAligned or mIsStashed changes.
|
||||
if (mIconAlignControllerLazy == null
|
||||
|| mIsHotseatIconOnTopWhenAligned != isHotseatIconOnTopWhenAligned) {
|
||||
|| mIsHotseatIconOnTopWhenAligned != isHotseatIconOnTopWhenAligned
|
||||
|| mIsStashed != isStashed) {
|
||||
mIsHotseatIconOnTopWhenAligned = isHotseatIconOnTopWhenAligned;
|
||||
mIsStashed = isStashed;
|
||||
mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
|
||||
}
|
||||
mIconAlignControllerLazy.setPlayFraction(alignmentRatio);
|
||||
@@ -443,8 +457,13 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
* 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);
|
||||
if (TaskbarManager.isPhoneButtonNavMode(mActivity)) {
|
||||
// No animation for icons in small-screen
|
||||
return setter.createPlaybackController();
|
||||
}
|
||||
|
||||
mOnControllerPreCreateCallback.run();
|
||||
DeviceProfile taskbarDp = mActivity.getDeviceProfile();
|
||||
Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
|
||||
float scaleUp = ((float) launcherDp.iconSizePx) / taskbarDp.taskbarIconSize;
|
||||
@@ -486,7 +505,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
|| (isTaskbarDividerView && FeatureFlags.ENABLE_TASKBAR_PINNING.get())) {
|
||||
if (!isToHome
|
||||
&& mIsHotseatIconOnTopWhenAligned
|
||||
&& mControllers.taskbarStashController.isStashed()) {
|
||||
&& mIsStashed) {
|
||||
// Prevent All Apps icon from appearing when going from hotseat to nav handle.
|
||||
setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0f, 0f));
|
||||
} else {
|
||||
@@ -676,6 +695,11 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
.updateAndAnimateIsManuallyStashedInApp(true);
|
||||
}
|
||||
|
||||
/** Gets the hover listener for the provided icon view. */
|
||||
public View.OnHoverListener getIconOnHoverListener(View icon) {
|
||||
return new TaskbarHoverToolTipController(mActivity, mTaskbarView, icon);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
||||
+4
-4
@@ -47,17 +47,17 @@ public class TaskbarAllAppsContainerView extends
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View inflateSearchBox() {
|
||||
protected View inflateSearchBar() {
|
||||
if (isSearchSupported()) {
|
||||
return super.inflateSearchBox();
|
||||
return super.inflateSearchBar();
|
||||
}
|
||||
|
||||
// Remove top padding of header, since we do not have any search
|
||||
mHeader.setPadding(mHeader.getPaddingLeft(), 0,
|
||||
mHeader.getPaddingRight(), mHeader.getPaddingBottom());
|
||||
|
||||
TaskbarAllAppsFallbackSearchContainer searchView =
|
||||
new TaskbarAllAppsFallbackSearchContainer(getContext(), null);
|
||||
TaskbarAllAppsFallbackSearchContainer searchView = new TaskbarAllAppsFallbackSearchContainer(getContext(),
|
||||
null);
|
||||
searchView.setId(R.id.search_container_all_apps);
|
||||
searchView.setVisibility(GONE);
|
||||
return searchView;
|
||||
|
||||
@@ -15,11 +15,17 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar.allapps;
|
||||
|
||||
import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.appprediction.PredictionRowView;
|
||||
import com.android.launcher3.dragndrop.DragOptions.PreDragCondition;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.taskbar.TaskbarControllers;
|
||||
@@ -34,24 +40,31 @@ import java.util.function.Predicate;
|
||||
/**
|
||||
* Handles the all apps overlay window initialization, updates, and its data.
|
||||
* <p>
|
||||
* All apps is in an application overlay window instead of taskbar's navigation bar panel window,
|
||||
* because a navigation bar panel is higher than UI components that all apps should be below such as
|
||||
* All apps is in an application overlay window instead of taskbar's navigation
|
||||
* bar panel window,
|
||||
* because a navigation bar panel is higher than UI components that all apps
|
||||
* should be below such as
|
||||
* the notification tray.
|
||||
* <p>
|
||||
* The all apps window is created and destroyed upon opening and closing all apps, respectively.
|
||||
* Application data may be bound while the window does not exist, so this controller will store
|
||||
* The all apps window is created and destroyed upon opening and closing all
|
||||
* apps, respectively.
|
||||
* Application data may be bound while the window does not exist, so this
|
||||
* controller will store
|
||||
* the models for the next all apps session.
|
||||
*/
|
||||
public final class TaskbarAllAppsController {
|
||||
|
||||
private TaskbarControllers mControllers;
|
||||
private @Nullable TaskbarOverlayContext mOverlayContext;
|
||||
private @Nullable TaskbarAllAppsSlideInView mSlideInView;
|
||||
private @Nullable TaskbarAllAppsContainerView mAppsView;
|
||||
private @Nullable TaskbarSearchSessionController mSearchSessionController;
|
||||
|
||||
// Application data models.
|
||||
private AppInfo[] mApps;
|
||||
private @NonNull AppInfo[] mApps = EMPTY_ARRAY;
|
||||
private int mAppsModelFlags;
|
||||
private List<ItemInfo> mPredictedApps;
|
||||
private @NonNull List<ItemInfo> mPredictedApps = Collections.emptyList();
|
||||
private @Nullable List<ItemInfo> mZeroStateSearchSuggestions;
|
||||
private boolean mDisallowGlobalDrag;
|
||||
private boolean mDisallowLongClick;
|
||||
|
||||
@@ -62,7 +75,8 @@ public final class TaskbarAllAppsController {
|
||||
mControllers = controllers;
|
||||
|
||||
/*
|
||||
* Recreate All Apps if it was open in the previous Taskbar instance (e.g. the configuration
|
||||
* Recreate All Apps if it was open in the previous Taskbar instance (e.g. the
|
||||
* configuration
|
||||
* changed).
|
||||
*/
|
||||
if (allAppsVisible) {
|
||||
@@ -70,9 +84,14 @@ public final class TaskbarAllAppsController {
|
||||
}
|
||||
}
|
||||
|
||||
/** Clean up the controller. */
|
||||
public void onDestroy() {
|
||||
cleanUpOverlay();
|
||||
}
|
||||
|
||||
/** Updates the current {@link AppInfo} instances. */
|
||||
public void setApps(AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map) {
|
||||
mApps = apps;
|
||||
public void setApps(@Nullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map) {
|
||||
mApps = apps == null ? EMPTY_ARRAY : apps;
|
||||
mAppsModelFlags = flags;
|
||||
mPackageUserKeytoUidMap = map;
|
||||
if (mAppsView != null) {
|
||||
@@ -96,6 +115,14 @@ public final class TaskbarAllAppsController {
|
||||
.findFixedRowByType(PredictionRowView.class)
|
||||
.setPredictedApps(mPredictedApps);
|
||||
}
|
||||
if (mSearchSessionController != null) {
|
||||
mSearchSessionController.setZeroStatePredictedItems(predictedApps);
|
||||
}
|
||||
}
|
||||
|
||||
/** Updates the current search suggestions. */
|
||||
public void setZeroStateSearchSuggestions(List<ItemInfo> zeroStateSearchSuggestions) {
|
||||
mZeroStateSearchSuggestions = zeroStateSearchSuggestions;
|
||||
}
|
||||
|
||||
/** Updates the current notification dots. */
|
||||
@@ -105,7 +132,10 @@ public final class TaskbarAllAppsController {
|
||||
}
|
||||
}
|
||||
|
||||
/** Toggles visibility of {@link TaskbarAllAppsContainerView} in the overlay window. */
|
||||
/**
|
||||
* Toggles visibility of {@link TaskbarAllAppsContainerView} in the overlay
|
||||
* window.
|
||||
*/
|
||||
public void toggle() {
|
||||
if (isOpen()) {
|
||||
mSlideInView.close(true);
|
||||
@@ -123,45 +153,87 @@ public final class TaskbarAllAppsController {
|
||||
if (mAppsView != null) {
|
||||
return;
|
||||
}
|
||||
// mControllers and getSharedState should never be null here. Do not handle null-pointer
|
||||
// mControllers and getSharedState should never be null here. Do not handle
|
||||
// null-pointer
|
||||
// to catch invalid states.
|
||||
mControllers.getSharedState().allAppsVisible = true;
|
||||
|
||||
TaskbarOverlayContext overlayContext =
|
||||
mControllers.taskbarOverlayController.requestWindow();
|
||||
mSlideInView = (TaskbarAllAppsSlideInView) overlayContext.getLayoutInflater().inflate(
|
||||
R.layout.taskbar_all_apps, overlayContext.getDragLayer(), false);
|
||||
mOverlayContext = mControllers.taskbarOverlayController.requestWindow();
|
||||
|
||||
// Initialize search session for All Apps.
|
||||
mSearchSessionController = TaskbarSearchSessionController.newInstance(mOverlayContext);
|
||||
mOverlayContext.setSearchSessionController(mSearchSessionController);
|
||||
mSearchSessionController.setZeroStatePredictedItems(mPredictedApps);
|
||||
if (mZeroStateSearchSuggestions != null) {
|
||||
mSearchSessionController.setZeroStateSearchSuggestions(mZeroStateSearchSuggestions);
|
||||
}
|
||||
mSearchSessionController.startLifecycle();
|
||||
|
||||
mSlideInView = (TaskbarAllAppsSlideInView) mOverlayContext.getLayoutInflater().inflate(
|
||||
R.layout.taskbar_all_apps_sheet, mOverlayContext.getDragLayer(), false);
|
||||
mSlideInView.addOnCloseListener(() -> {
|
||||
mControllers.getSharedState().allAppsVisible = false;
|
||||
mSlideInView = null;
|
||||
mAppsView = null;
|
||||
cleanUpOverlay();
|
||||
});
|
||||
TaskbarAllAppsViewController viewController = new TaskbarAllAppsViewController(
|
||||
overlayContext, mSlideInView, mControllers);
|
||||
mOverlayContext, mSlideInView, mControllers, mSearchSessionController);
|
||||
|
||||
viewController.show(animate);
|
||||
mAppsView = overlayContext.getAppsView();
|
||||
mAppsView = mOverlayContext.getAppsView();
|
||||
mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap);
|
||||
mAppsView.getFloatingHeaderView()
|
||||
.findFixedRowByType(PredictionRowView.class)
|
||||
.setPredictedApps(mPredictedApps);
|
||||
// 1 alternative that would be more work:
|
||||
// Create a shared drag layer between taskbar and taskbarAllApps so that when dragging
|
||||
// starts and taskbarAllApps can close, but the drag layer that the view is being dragged in
|
||||
// Create a shared drag layer between taskbar and taskbarAllApps so that when
|
||||
// dragging
|
||||
// starts and taskbarAllApps can close, but the drag layer that the view is
|
||||
// being dragged in
|
||||
// doesn't also close
|
||||
overlayContext.getDragController().setDisallowGlobalDrag(mDisallowGlobalDrag);
|
||||
overlayContext.getDragController().setDisallowLongClick(mDisallowLongClick);
|
||||
mOverlayContext.getDragController().setDisallowGlobalDrag(mDisallowGlobalDrag);
|
||||
mOverlayContext.getDragController().setDisallowLongClick(mDisallowLongClick);
|
||||
}
|
||||
|
||||
private void cleanUpOverlay() {
|
||||
// Floating search bar is added to the drag layer in
|
||||
// ActivityAllAppsContainerView onAttach;
|
||||
// removed here as this is a special case that we remove the all apps panel.
|
||||
if (mAppsView != null && mOverlayContext != null
|
||||
&& mAppsView.getSearchUiDelegate().isSearchBarFloating()) {
|
||||
mOverlayContext.getDragLayer().removeView(mAppsView.getSearchView());
|
||||
mAppsView.getSearchUiDelegate().onDestroySearchBar();
|
||||
}
|
||||
if (mSearchSessionController != null) {
|
||||
mSearchSessionController.onDestroy();
|
||||
mSearchSessionController = null;
|
||||
}
|
||||
if (mOverlayContext != null) {
|
||||
mOverlayContext.setSearchSessionController(null);
|
||||
mOverlayContext = null;
|
||||
}
|
||||
mSlideInView = null;
|
||||
mAppsView = null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public int getTaskbarAllAppsTopPadding() {
|
||||
// Allow null-pointer since this should only be null if the apps view is not showing.
|
||||
// Allow null-pointer since this should only be null if the apps view is not
|
||||
// showing.
|
||||
return mAppsView.getActiveRecyclerView().getClipBounds().top;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public int getTaskbarAllAppsScroll() {
|
||||
// Allow null-pointer since this should only be null if the apps view is not showing.
|
||||
// Allow null-pointer since this should only be null if the apps view is not
|
||||
// showing.
|
||||
return mAppsView.getActiveRecyclerView().computeVerticalScrollOffset();
|
||||
}
|
||||
|
||||
/** @see TaskbarSearchSessionController#createPreDragConditionForSearch(View) */
|
||||
@Nullable
|
||||
public PreDragCondition createPreDragConditionForSearch(View view) {
|
||||
return mSearchSessionController != null
|
||||
? mSearchSessionController.createPreDragConditionForSearch(view)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
+72
-10
@@ -15,20 +15,27 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar.allapps;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
|
||||
import static com.android.app.animation.Interpolators.EMPHASIZED;
|
||||
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.animation.Animator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.window.OnBackInvokedDispatcher;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Insettable;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsViewController.TaskbarAllAppsCallbacks;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
|
||||
@@ -37,8 +44,11 @@ import com.android.launcher3.views.AbstractSlideInView;
|
||||
/** Wrapper for taskbar all apps with slide-in behavior. */
|
||||
public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverlayContext>
|
||||
implements Insettable, DeviceProfile.OnDeviceProfileChangeListener {
|
||||
private final Handler mHandler;
|
||||
|
||||
private TaskbarAllAppsContainerView mAppsView;
|
||||
private float mShiftRange;
|
||||
private @Nullable Runnable mShowOnFullyAttachedToWindowRunnable;
|
||||
|
||||
// Initialized in init.
|
||||
private TaskbarAllAppsCallbacks mAllAppsCallbacks;
|
||||
@@ -50,6 +60,7 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
|
||||
public TaskbarAllAppsSlideInView(Context context, AttributeSet attrs,
|
||||
int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mHandler = new Handler(Looper.myLooper());
|
||||
}
|
||||
|
||||
void init(TaskbarAllAppsCallbacks callbacks) {
|
||||
@@ -58,20 +69,51 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
|
||||
|
||||
/** Opens the all apps view. */
|
||||
void show(boolean animate) {
|
||||
if (mIsOpen || mOpenCloseAnimator.isRunning()) {
|
||||
if (mIsOpen || mOpenCloseAnimation.getAnimationPlayer().isRunning()) {
|
||||
return;
|
||||
}
|
||||
mIsOpen = true;
|
||||
attachToContainer();
|
||||
|
||||
if (animate) {
|
||||
mOpenCloseAnimator.setValues(
|
||||
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
|
||||
mOpenCloseAnimator.setInterpolator(EMPHASIZED);
|
||||
mOpenCloseAnimator.setDuration(mAllAppsCallbacks.getOpenDuration()).start();
|
||||
} else {
|
||||
addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
|
||||
@Override
|
||||
public void onViewAttachedToWindow(View v) {
|
||||
removeOnAttachStateChangeListener(this);
|
||||
// Wait for view and its descendants to be fully attached before starting open.
|
||||
mShowOnFullyAttachedToWindowRunnable = () -> showOnFullyAttachedToWindow(animate);
|
||||
mHandler.post(mShowOnFullyAttachedToWindowRunnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(View v) {
|
||||
removeOnAttachStateChangeListener(this);
|
||||
}
|
||||
});
|
||||
attachToContainer();
|
||||
}
|
||||
|
||||
private void showOnFullyAttachedToWindow(boolean animate) {
|
||||
mAllAppsCallbacks.onAllAppsTransitionStart(true);
|
||||
if (!animate) {
|
||||
mAllAppsCallbacks.onAllAppsTransitionEnd(true);
|
||||
mTranslationShift = TRANSLATION_SHIFT_OPENED;
|
||||
return;
|
||||
}
|
||||
|
||||
setUpOpenAnimation(mAllAppsCallbacks.getOpenDuration());
|
||||
Animator animator = mOpenCloseAnimation.getAnimationPlayer();
|
||||
animator.setInterpolator(EMPHASIZED);
|
||||
animator.addListener(AnimatorListeners.forEndCallback(() -> {
|
||||
if (mIsOpen) {
|
||||
mAllAppsCallbacks.onAllAppsTransitionEnd(true);
|
||||
}
|
||||
}));
|
||||
animator.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onOpenCloseAnimationPending(PendingAnimation animation) {
|
||||
mAllAppsCallbacks.onAllAppsAnimationPending(
|
||||
animation, mToTranslationShift == TRANSLATION_SHIFT_OPENED);
|
||||
}
|
||||
|
||||
/** The apps container inside this view. */
|
||||
@@ -81,9 +123,22 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
|
||||
|
||||
@Override
|
||||
protected void handleClose(boolean animate) {
|
||||
if (mShowOnFullyAttachedToWindowRunnable != null) {
|
||||
mHandler.removeCallbacks(mShowOnFullyAttachedToWindowRunnable);
|
||||
mShowOnFullyAttachedToWindowRunnable = null;
|
||||
}
|
||||
if (mIsOpen) {
|
||||
mAllAppsCallbacks.onAllAppsTransitionStart(false);
|
||||
}
|
||||
handleClose(animate, mAllAppsCallbacks.getCloseDuration());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCloseComplete() {
|
||||
mAllAppsCallbacks.onAllAppsTransitionEnd(false);
|
||||
super.onCloseComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Interpolator getIdleInterpolator() {
|
||||
return EMPHASIZED;
|
||||
@@ -194,4 +249,11 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
|
||||
protected boolean isEventOverContent(MotionEvent ev) {
|
||||
return getPopupContainer().isEventOverView(mAppsView.getVisibleContainerView(), ev);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackInvoked() {
|
||||
if (!mAllAppsCallbacks.handleSearchBackInvoked()) {
|
||||
super.onBackInvoked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+31
-14
@@ -19,8 +19,9 @@ import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_
|
||||
import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.allapps.AllAppsTransitionListener;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.appprediction.AppsDividerView;
|
||||
import com.android.launcher3.appprediction.PredictionRowView;
|
||||
import com.android.launcher3.taskbar.NavbarButtonsViewController;
|
||||
import com.android.launcher3.taskbar.TaskbarControllers;
|
||||
import com.android.launcher3.taskbar.TaskbarStashController;
|
||||
@@ -44,7 +45,8 @@ final class TaskbarAllAppsViewController {
|
||||
TaskbarAllAppsViewController(
|
||||
TaskbarOverlayContext context,
|
||||
TaskbarAllAppsSlideInView slideInView,
|
||||
TaskbarControllers taskbarControllers) {
|
||||
TaskbarControllers taskbarControllers,
|
||||
TaskbarSearchSessionController searchSessionController) {
|
||||
|
||||
mContext = context;
|
||||
mSlideInView = slideInView;
|
||||
@@ -53,8 +55,7 @@ final class TaskbarAllAppsViewController {
|
||||
mNavbarButtonsViewController = taskbarControllers.navbarButtonsViewController;
|
||||
mOverlayController = taskbarControllers.taskbarOverlayController;
|
||||
|
||||
mSlideInView.init(new TaskbarAllAppsCallbacks());
|
||||
setUpIconLongClick();
|
||||
mSlideInView.init(new TaskbarAllAppsCallbacks(searchSessionController));
|
||||
setUpAppDivider();
|
||||
setUpTaskbarStashing();
|
||||
}
|
||||
@@ -69,15 +70,6 @@ final class TaskbarAllAppsViewController {
|
||||
mSlideInView.close(animate);
|
||||
}
|
||||
|
||||
private void setUpIconLongClick() {
|
||||
mAppsView.setOnIconLongClickListener(
|
||||
mContext.getDragController()::startDragOnLongClick);
|
||||
mAppsView.getFloatingHeaderView()
|
||||
.findFixedRowByType(PredictionRowView.class)
|
||||
.setOnIconLongClickListener(
|
||||
mContext.getDragController()::startDragOnLongClick);
|
||||
}
|
||||
|
||||
private void setUpAppDivider() {
|
||||
mAppsView.getFloatingHeaderView()
|
||||
.findFixedRowByType(AppsDividerView.class)
|
||||
@@ -105,7 +97,13 @@ final class TaskbarAllAppsViewController {
|
||||
});
|
||||
}
|
||||
|
||||
class TaskbarAllAppsCallbacks {
|
||||
class TaskbarAllAppsCallbacks implements AllAppsTransitionListener {
|
||||
private final TaskbarSearchSessionController mSearchSessionController;
|
||||
|
||||
private TaskbarAllAppsCallbacks(TaskbarSearchSessionController searchSessionController) {
|
||||
mSearchSessionController = searchSessionController;
|
||||
}
|
||||
|
||||
int getOpenDuration() {
|
||||
return mOverlayController.getOpenDuration();
|
||||
}
|
||||
@@ -113,5 +111,24 @@ final class TaskbarAllAppsViewController {
|
||||
int getCloseDuration() {
|
||||
return mOverlayController.getCloseDuration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllAppsTransitionStart(boolean toAllApps) {
|
||||
mSearchSessionController.onAllAppsTransitionStart(toAllApps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllAppsTransitionEnd(boolean toAllApps) {
|
||||
mSearchSessionController.onAllAppsTransitionEnd(toAllApps);
|
||||
}
|
||||
|
||||
/** Invoked on back press, returning {@code true} if the search session handled it. */
|
||||
boolean handleSearchBackInvoked() {
|
||||
return mSearchSessionController.handleBackInvoked();
|
||||
}
|
||||
|
||||
void onAllAppsAnimationPending(PendingAnimation animation, boolean toAllApps) {
|
||||
mSearchSessionController.onAllAppsAnimationPending(animation, toAllApps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.allapps
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.allapps.AllAppsTransitionListener
|
||||
import com.android.launcher3.anim.PendingAnimation
|
||||
import com.android.launcher3.config.FeatureFlags
|
||||
import com.android.launcher3.dragndrop.DragOptions.PreDragCondition
|
||||
import com.android.launcher3.model.data.ItemInfo
|
||||
import com.android.launcher3.util.ResourceBasedOverride
|
||||
import com.android.launcher3.util.ResourceBasedOverride.Overrides
|
||||
|
||||
/** Stub for managing the Taskbar search session. */
|
||||
open class TaskbarSearchSessionController : ResourceBasedOverride, AllAppsTransitionListener {
|
||||
|
||||
/** Start the search session lifecycle. */
|
||||
open fun startLifecycle() = Unit
|
||||
|
||||
/** Destroy the search session. */
|
||||
open fun onDestroy() = Unit
|
||||
|
||||
/** Updates the predicted items shown in the zero-state. */
|
||||
open fun setZeroStatePredictedItems(items: List<ItemInfo>) = Unit
|
||||
|
||||
/** Updates the search suggestions shown in the zero-state. */
|
||||
open fun setZeroStateSearchSuggestions(items: List<ItemInfo>) = Unit
|
||||
|
||||
override fun onAllAppsTransitionStart(toAllApps: Boolean) = Unit
|
||||
|
||||
override fun onAllAppsTransitionEnd(toAllApps: Boolean) = Unit
|
||||
|
||||
/** Creates a [PreDragCondition] for [view], if it is a search result that requires one. */
|
||||
open fun createPreDragConditionForSearch(view: View): PreDragCondition? = null
|
||||
|
||||
open fun handleBackInvoked(): Boolean = false
|
||||
|
||||
open fun onAllAppsAnimationPending(animation: PendingAnimation, toAllApps: Boolean) = Unit
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newInstance(context: Context): TaskbarSearchSessionController {
|
||||
if (!FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
|
||||
return TaskbarSearchSessionController()
|
||||
}
|
||||
|
||||
return Overrides.getObject(
|
||||
TaskbarSearchSessionController::class.java,
|
||||
context,
|
||||
R.string.taskbar_search_session_controller_class,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,10 +21,10 @@ import android.graphics.ColorFilter
|
||||
import android.graphics.Paint
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.ShapeDrawable
|
||||
import com.android.app.animation.Interpolators
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.Utilities
|
||||
import com.android.launcher3.Utilities.mapToRange
|
||||
import com.android.launcher3.anim.Interpolators
|
||||
import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext
|
||||
import com.android.wm.shell.common.TriangleShape
|
||||
@@ -48,6 +48,8 @@ class BubbleBarBackground(context: TaskbarActivityContext, private val backgroun
|
||||
private var showingArrow: Boolean = false
|
||||
private var arrowDrawable: ShapeDrawable
|
||||
|
||||
var width: Float = 0f
|
||||
|
||||
init {
|
||||
paint.color = context.getColor(R.color.taskbar_background)
|
||||
paint.flags = Paint.ANTI_ALIAS_FLAG
|
||||
@@ -59,8 +61,11 @@ class BubbleBarBackground(context: TaskbarActivityContext, private val backgroun
|
||||
pointerSize = res.getDimension(R.dimen.bubblebar_pointer_size)
|
||||
|
||||
shadowAlpha =
|
||||
if (Utilities.isDarkTheme(context)) DARK_THEME_SHADOW_ALPHA
|
||||
else LIGHT_THEME_SHADOW_ALPHA
|
||||
if (Utilities.isDarkTheme(context)) {
|
||||
DARK_THEME_SHADOW_ALPHA
|
||||
} else {
|
||||
LIGHT_THEME_SHADOW_ALPHA
|
||||
}
|
||||
|
||||
arrowDrawable =
|
||||
ShapeDrawable(TriangleShape.create(pointerSize, pointerSize, /* pointUp= */ true))
|
||||
@@ -102,7 +107,7 @@ class BubbleBarBackground(context: TaskbarActivityContext, private val backgroun
|
||||
// Draw background.
|
||||
val radius = backgroundHeight / 2f
|
||||
canvas.drawRoundRect(
|
||||
0f,
|
||||
canvas.width.toFloat() - width,
|
||||
0f,
|
||||
canvas.width.toFloat(),
|
||||
canvas.height.toFloat(),
|
||||
@@ -132,4 +137,8 @@ class BubbleBarBackground(context: TaskbarActivityContext, private val backgroun
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
paint.colorFilter = colorFilter
|
||||
}
|
||||
|
||||
fun setArrowAlpha(alpha: Int) {
|
||||
arrowDrawable.paint.alpha = alpha
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,19 +31,27 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_Q
|
||||
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 static java.lang.Math.abs;
|
||||
|
||||
import android.annotation.BinderThread;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.drawable.AdaptiveIconDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
@@ -51,6 +59,8 @@ import android.util.Log;
|
||||
import android.util.PathParser;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
|
||||
import com.android.internal.graphics.ColorUtils;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
@@ -71,10 +81,13 @@ import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* This registers a listener with SysUIProxy to get information about changes to the bubble
|
||||
* stack state from WMShell (SysUI). The controller is also responsible for loading the necessary
|
||||
* This registers a listener with SysUIProxy to get information about changes to
|
||||
* the bubble
|
||||
* stack state from WMShell (SysUI). The controller is also responsible for
|
||||
* loading the necessary
|
||||
* information to render each of the bubbles & dispatches changes to
|
||||
* {@link BubbleBarViewController} which will then update {@link BubbleBarView} as needed.
|
||||
* {@link BubbleBarViewController} which will then update {@link BubbleBarView}
|
||||
* as needed.
|
||||
*
|
||||
* For details around the behavior of the bubble bar, see {@link BubbleBarView}.
|
||||
*/
|
||||
@@ -84,8 +97,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
// Whether bubbles are showing in the bubble bar from launcher
|
||||
public static final boolean BUBBLE_BAR_ENABLED =
|
||||
SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
|
||||
public static final boolean BUBBLE_BAR_ENABLED = SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
|
||||
|
||||
private static final int MASK_HIDE_BUBBLE_BAR = SYSUI_STATE_BOUNCER_SHOWING
|
||||
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
|
||||
@@ -112,15 +124,18 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
private final Executor mMainExecutor;
|
||||
private final LauncherApps mLauncherApps;
|
||||
private final BubbleIconFactory mIconFactory;
|
||||
private final SystemUiProxy mSystemUiProxy;
|
||||
|
||||
private BubbleBarBubble mSelectedBubble;
|
||||
private BubbleBarItem mSelectedBubble;
|
||||
private BubbleBarOverflow mOverflowBubble;
|
||||
|
||||
private BubbleBarViewController mBubbleBarViewController;
|
||||
private BubbleStashController mBubbleStashController;
|
||||
private BubbleStashedHandleViewController mBubbleStashedHandleViewController;
|
||||
|
||||
/**
|
||||
* Similar to {@link BubbleBarUpdate} but rather than {@link BubbleInfo}s it uses
|
||||
* Similar to {@link BubbleBarUpdate} but rather than {@link BubbleInfo}s it
|
||||
* uses
|
||||
* {@link BubbleBarBubble}s so that it can be used to update the views.
|
||||
*/
|
||||
private static class BubbleBarViewUpdate {
|
||||
@@ -152,8 +167,10 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
mContext = context;
|
||||
mBarView = bubbleView; // Need the view for inflating bubble views.
|
||||
|
||||
mSystemUiProxy = SystemUiProxy.INSTANCE.get(context);
|
||||
|
||||
if (BUBBLE_BAR_ENABLED) {
|
||||
SystemUiProxy.INSTANCE.get(context).setBubblesListener(this);
|
||||
mSystemUiProxy.setBubblesListener(this);
|
||||
}
|
||||
mMainExecutor = MAIN_EXECUTOR;
|
||||
mLauncherApps = context.getSystemService(LauncherApps.class);
|
||||
@@ -166,7 +183,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
SystemUiProxy.INSTANCE.get(mContext).setBubblesListener(null);
|
||||
mSystemUiProxy.setBubblesListener(null);
|
||||
}
|
||||
|
||||
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
|
||||
@@ -177,11 +194,38 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
bubbleControllers.runAfterInit(() -> {
|
||||
mBubbleBarViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
|
||||
mBubbleStashedHandleViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
|
||||
mBubbleBarViewController.setUpdateSelectedBubbleAfterCollapse(
|
||||
key -> setSelectedBubble(mBubbles.get(key)));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the bubble bar, handle bar, and stash controllers based on sysui state flags.
|
||||
* Creates and adds the overflow bubble to the bubble bar if it hasn't been
|
||||
* created yet.
|
||||
*
|
||||
* <p>
|
||||
* This should be called on the {@link #BUBBLE_STATE_EXECUTOR} executor to avoid
|
||||
* inflating
|
||||
* the overflow multiple times.
|
||||
*/
|
||||
private void createAndAddOverflowIfNeeded() {
|
||||
if (mOverflowBubble == null) {
|
||||
BubbleBarOverflow overflow = createOverflow(mContext);
|
||||
mMainExecutor.execute(() -> {
|
||||
// we're on the main executor now, so check that the overflow hasn't been
|
||||
// created
|
||||
// again to avoid races.
|
||||
if (mOverflowBubble == null) {
|
||||
mBubbleBarViewController.addBubble(overflow);
|
||||
mOverflowBubble = overflow;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the bubble bar, handle bar, and stash controllers based on sysui
|
||||
* state flags.
|
||||
*/
|
||||
public void updateStateForSysuiFlags(int flags) {
|
||||
boolean hideBubbleBar = (flags & MASK_HIDE_BUBBLE_BAR) != 0;
|
||||
@@ -209,18 +253,21 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
|| !update.currentBubbleList.isEmpty()) {
|
||||
// We have bubbles to load
|
||||
BUBBLE_STATE_EXECUTOR.execute(() -> {
|
||||
createAndAddOverflowIfNeeded();
|
||||
if (update.addedBubble != null) {
|
||||
viewUpdate.addedBubble = populateBubble(update.addedBubble, mContext, mBarView);
|
||||
viewUpdate.addedBubble = populateBubble(mContext, update.addedBubble, mBarView,
|
||||
null /* existingBubble */);
|
||||
}
|
||||
if (update.updatedBubble != null) {
|
||||
viewUpdate.updatedBubble =
|
||||
populateBubble(update.updatedBubble, mContext, mBarView);
|
||||
BubbleBarBubble existingBubble = mBubbles.get(update.updatedBubble.getKey());
|
||||
viewUpdate.updatedBubble = populateBubble(mContext, update.updatedBubble, mBarView,
|
||||
existingBubble);
|
||||
}
|
||||
if (update.currentBubbleList != null && !update.currentBubbleList.isEmpty()) {
|
||||
List<BubbleBarBubble> currentBubbles = new ArrayList<>();
|
||||
for (int i = 0; i < update.currentBubbleList.size(); i++) {
|
||||
BubbleBarBubble b =
|
||||
populateBubble(update.currentBubbleList.get(i), mContext, mBarView);
|
||||
BubbleBarBubble b = populateBubble(mContext, update.currentBubbleList.get(i), mBarView,
|
||||
null /* existingBubble */);
|
||||
currentBubbles.add(b);
|
||||
}
|
||||
viewUpdate.currentBubbles = currentBubbles;
|
||||
@@ -237,6 +284,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
private void applyViewChanges(BubbleBarViewUpdate update) {
|
||||
final boolean isCollapsed = (update.expandedChanged && !update.expanded)
|
||||
|| (!update.expandedChanged && !mBubbleBarViewController.isExpanded());
|
||||
BubbleBarItem previouslySelectedBubble = mSelectedBubble;
|
||||
BubbleBarBubble bubbleToSelect = null;
|
||||
if (!update.removedBubbles.isEmpty()) {
|
||||
for (int i = 0; i < update.removedBubbles.size(); i++) {
|
||||
@@ -260,7 +308,8 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
|
||||
}
|
||||
if (update.currentBubbles != null && !update.currentBubbles.isEmpty()) {
|
||||
// Iterate in reverse because new bubbles are added in front and the list is in order.
|
||||
// Iterate in reverse because new bubbles are added in front and the list is in
|
||||
// order.
|
||||
for (int i = update.currentBubbles.size() - 1; i >= 0; i--) {
|
||||
BubbleBarBubble bubble = update.currentBubbles.get(i);
|
||||
if (bubble != null) {
|
||||
@@ -277,14 +326,23 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
// Adds and removals have happened, update visibility before any other visual changes.
|
||||
// Adds and removals have happened, update visibility before any other visual
|
||||
// changes.
|
||||
mBubbleBarViewController.setHiddenForBubbles(mBubbles.isEmpty());
|
||||
mBubbleStashedHandleViewController.setHiddenForBubbles(mBubbles.isEmpty());
|
||||
|
||||
if (mBubbles.isEmpty()) {
|
||||
// all bubbles were removed. clear the selected bubble
|
||||
mSelectedBubble = null;
|
||||
}
|
||||
|
||||
if (update.updatedBubble != null) {
|
||||
// TODO: (b/269670235) handle updates:
|
||||
// (1) if content / icons change -- requires reload & add back in place
|
||||
// (2) if showing update dot changes -- tell the view to hide / show the dot
|
||||
// Updates mean the dot state may have changed; any other changes were updated
|
||||
// in
|
||||
// the populateBubble step.
|
||||
BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey());
|
||||
// If we're not stashed, we're visible so animate
|
||||
bb.getView().updateDotVisibility(!mBubbleStashController.isStashed() /* animate */);
|
||||
}
|
||||
if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) {
|
||||
// Create the new list
|
||||
@@ -301,8 +359,8 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
// TODO: (b/273316505) handle suppression
|
||||
}
|
||||
if (update.selectedBubbleKey != null) {
|
||||
if (mSelectedBubble != null
|
||||
&& !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
|
||||
if (mSelectedBubble == null
|
||||
|| !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
|
||||
BubbleBarBubble newlySelected = mBubbles.get(update.selectedBubbleKey);
|
||||
if (newlySelected != null) {
|
||||
bubbleToSelect = newlySelected;
|
||||
@@ -314,7 +372,11 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
}
|
||||
if (bubbleToSelect != null) {
|
||||
setSelectedBubble(bubbleToSelect);
|
||||
if (previouslySelectedBubble == null) {
|
||||
mBubbleStashController.animateToInitialState(update.expanded);
|
||||
}
|
||||
}
|
||||
|
||||
if (update.expandedChanged) {
|
||||
if (update.expanded != mBubbleBarViewController.isExpanded()) {
|
||||
mBubbleBarViewController.setExpandedFromSysui(update.expanded);
|
||||
@@ -324,14 +386,47 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
/** Tells WMShell to show the currently selected bubble. */
|
||||
public void showSelectedBubble() {
|
||||
if (getSelectedBubbleKey() != null) {
|
||||
if (mSelectedBubble instanceof BubbleBarBubble) {
|
||||
// Because we've visited this bubble, we should suppress the notification.
|
||||
// This is updated on WMShell side when we show the bubble, but that update
|
||||
// isn't
|
||||
// passed to launcher, instead we apply it directly here.
|
||||
BubbleInfo info = ((BubbleBarBubble) mSelectedBubble).getInfo();
|
||||
info.setFlags(
|
||||
info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
|
||||
mSelectedBubble.getView().updateDotVisibility(true /* animate */);
|
||||
}
|
||||
mSystemUiProxy.showBubble(getSelectedBubbleKey(),
|
||||
getBubbleBarOffsetX(), getBubbleBarOffsetY());
|
||||
} else {
|
||||
Log.w(TAG, "Trying to show the selected bubble but it's null");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bubble that should be selected. This notifies the views, it does not notify
|
||||
* WMShell that the selection has changed, that should go through
|
||||
* {@link SystemUiProxy#showBubble}.
|
||||
* Updates the currently selected bubble for launcher views and tells WMShell to
|
||||
* show it.
|
||||
*/
|
||||
public void setSelectedBubble(BubbleBarBubble b) {
|
||||
public void showAndSelectBubble(BubbleBarItem b) {
|
||||
if (DEBUG)
|
||||
Log.w(TAG, "showingSelectedBubble: " + b.getKey());
|
||||
setSelectedBubble(b);
|
||||
showSelectedBubble();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bubble that should be selected. This notifies the views, it does not
|
||||
* notify
|
||||
* WMShell that the selection has changed, that should go through either
|
||||
* {@link #showSelectedBubble()} or {@link #showAndSelectBubble(BubbleBarItem)}.
|
||||
*/
|
||||
private void setSelectedBubble(BubbleBarItem b) {
|
||||
if (!Objects.equals(b, mSelectedBubble)) {
|
||||
if (DEBUG) Log.w(TAG, "selectingBubble: " + b.getKey());
|
||||
if (DEBUG)
|
||||
Log.w(TAG, "selectingBubble: " + b.getKey());
|
||||
mSelectedBubble = b;
|
||||
mBubbleBarViewController.updateSelectedBubble(mSelectedBubble);
|
||||
}
|
||||
@@ -353,7 +448,8 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
//
|
||||
|
||||
@Nullable
|
||||
private BubbleBarBubble populateBubble(BubbleInfo b, Context context, BubbleBarView bbv) {
|
||||
private BubbleBarBubble populateBubble(Context context, BubbleInfo b, BubbleBarView bbv,
|
||||
@Nullable BubbleBarBubble existingBubble) {
|
||||
String appName;
|
||||
Bitmap badgeBitmap;
|
||||
Bitmap bubbleBitmap;
|
||||
@@ -422,16 +518,68 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
iconPath.transform(matrix);
|
||||
dotPath = iconPath;
|
||||
dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
|
||||
Color.WHITE, WHITE_SCRIM_ALPHA);
|
||||
Color.WHITE, WHITE_SCRIM_ALPHA / 255f);
|
||||
|
||||
if (existingBubble == null) {
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
BubbleView bubbleView = (BubbleView) inflater.inflate(
|
||||
R.layout.bubblebar_item_view, bbv, false /* attachToRoot */);
|
||||
|
||||
BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView,
|
||||
badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
|
||||
bubbleView.setBubble(bubble);
|
||||
return bubble;
|
||||
} else {
|
||||
// If we already have a bubble (so it already has an inflated view), update it.
|
||||
existingBubble.setInfo(b);
|
||||
existingBubble.setBadge(badgeBitmap);
|
||||
existingBubble.setIcon(bubbleBitmap);
|
||||
existingBubble.setDotColor(dotColor);
|
||||
existingBubble.setDotPath(dotPath);
|
||||
existingBubble.setAppName(appName);
|
||||
return existingBubble;
|
||||
}
|
||||
}
|
||||
|
||||
private BubbleBarOverflow createOverflow(Context context) {
|
||||
Bitmap bitmap = createOverflowBitmap(context);
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
BubbleView bubbleView = (BubbleView) inflater.inflate(
|
||||
R.layout.bubblebar_item_view, bbv, false /* attachToRoot */);
|
||||
R.layout.bubblebar_item_view, mBarView, false /* attachToRoot */);
|
||||
BubbleBarOverflow overflow = new BubbleBarOverflow(bubbleView);
|
||||
bubbleView.setOverflow(overflow, bitmap);
|
||||
return overflow;
|
||||
}
|
||||
|
||||
BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView,
|
||||
badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
|
||||
bubbleView.setBubble(bubble);
|
||||
return bubble;
|
||||
private Bitmap createOverflowBitmap(Context context) {
|
||||
Drawable iconDrawable = AppCompatResources.getDrawable(mContext,
|
||||
R.drawable.bubble_ic_overflow_button);
|
||||
|
||||
final TypedArray ta = mContext.obtainStyledAttributes(
|
||||
new int[] {
|
||||
com.android.internal.R.attr.materialColorOnPrimaryFixed,
|
||||
com.android.internal.R.attr.materialColorPrimaryFixed
|
||||
});
|
||||
int overflowIconColor = ta.getColor(0, Color.WHITE);
|
||||
int overflowBackgroundColor = ta.getColor(1, Color.BLACK);
|
||||
ta.recycle();
|
||||
|
||||
iconDrawable.setTint(overflowIconColor);
|
||||
|
||||
int inset = context.getResources().getDimensionPixelSize(R.dimen.bubblebar_overflow_inset);
|
||||
Drawable foreground = new InsetDrawable(iconDrawable, inset);
|
||||
Drawable drawable = new AdaptiveIconDrawable(new ColorDrawable(overflowBackgroundColor),
|
||||
foreground);
|
||||
|
||||
return mIconFactory.createBadgedIconBitmap(drawable, Process.myUserHandle(), true).icon;
|
||||
}
|
||||
|
||||
private int getBubbleBarOffsetY() {
|
||||
final int translation = (int) abs(mBubbleStashController.getBubbleBarTranslationY());
|
||||
return translation + mBarView.getHeight();
|
||||
}
|
||||
|
||||
private int getBubbleBarOffsetX() {
|
||||
return mBarView.getWidth() + mBarView.getHorizontalMargin();
|
||||
}
|
||||
}
|
||||
|
||||
+13
-10
@@ -19,16 +19,19 @@ import android.graphics.Bitmap
|
||||
import android.graphics.Path
|
||||
import com.android.wm.shell.common.bubbles.BubbleInfo
|
||||
|
||||
/** An entity in the bubble bar. */
|
||||
sealed class BubbleBarItem(open var key: String, open var view: BubbleView)
|
||||
|
||||
/** Contains state info about a bubble in the bubble bar as well as presentation information. */
|
||||
data class BubbleBarBubble(
|
||||
val info: BubbleInfo,
|
||||
val view: BubbleView,
|
||||
val badge: Bitmap,
|
||||
val icon: Bitmap,
|
||||
val dotColor: Int,
|
||||
val dotPath: Path,
|
||||
val appName: String
|
||||
) {
|
||||
var info: BubbleInfo,
|
||||
override var view: BubbleView,
|
||||
var badge: Bitmap,
|
||||
var icon: Bitmap,
|
||||
var dotColor: Int,
|
||||
var dotPath: Path,
|
||||
var appName: String
|
||||
) : BubbleBarItem(info.key, view)
|
||||
|
||||
val key: String = info.key
|
||||
}
|
||||
/** Represents the overflow bubble in the bubble bar. */
|
||||
data class BubbleBarOverflow(override var view: BubbleView) : BubbleBarItem("Overflow", view)
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar.bubbles;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
@@ -31,46 +32,64 @@ import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* The view that holds all the bubble views. Modifying this view should happen through
|
||||
* {@link BubbleBarViewController}. Updates to the bubbles themselves (adds, removes, updates,
|
||||
* selection) should happen through {@link BubbleBarController} which is the source of truth
|
||||
* The view that holds all the bubble views. Modifying this view should happen
|
||||
* through
|
||||
* {@link BubbleBarViewController}. Updates to the bubbles themselves (adds,
|
||||
* removes, updates,
|
||||
* selection) should happen through {@link BubbleBarController} which is the
|
||||
* source of truth
|
||||
* for state information about the bubbles.
|
||||
* <p>
|
||||
* The bubble bar has a couple of visual states:
|
||||
* - stashed as a handle
|
||||
* - unstashed but collapsed, in this state the bar is showing but the bubbles are stacked within it
|
||||
* - unstashed and expanded, in this state the bar is showing and the bubbles are shown in a row
|
||||
* with one of the bubbles being selected. Additionally, WMShell will display the expanded bubble
|
||||
* - unstashed but collapsed, in this state the bar is showing but the bubbles
|
||||
* are stacked within it
|
||||
* - unstashed and expanded, in this state the bar is showing and the bubbles
|
||||
* are shown in a row
|
||||
* with one of the bubbles being selected. Additionally, WMShell will display
|
||||
* the expanded bubble
|
||||
* view above the bar.
|
||||
* <p>
|
||||
* The bubble bar has some behavior related to taskbar:
|
||||
* - When taskbar is unstashed, bubble bar will also become unstashed (but in its "collapsed"
|
||||
* - When taskbar is unstashed, bubble bar will also become unstashed (but in
|
||||
* its "collapsed"
|
||||
* state)
|
||||
* - When taskbar is stashed, bubble bar will also become stashed (unless bubble bar is in its
|
||||
* - When taskbar is stashed, bubble bar will also become stashed (unless bubble
|
||||
* bar is in its
|
||||
* "expanded" state)
|
||||
* - When bubble bar is in its "expanded" state, taskbar becomes stashed
|
||||
* <p>
|
||||
* If there are no bubbles, the bubble bar and bubble stashed handle are not shown. Additionally
|
||||
* If there are no bubbles, the bubble bar and bubble stashed handle are not
|
||||
* shown. Additionally
|
||||
* the bubble bar and stashed handle are not shown on lockscreen.
|
||||
* <p>
|
||||
* When taskbar is in persistent or 3 button nav mode, the bubble bar is not available, and instead
|
||||
* When taskbar is in persistent or 3 button nav mode, the bubble bar is not
|
||||
* available, and instead
|
||||
* the bubbles are shown fully by WMShell in their floating mode.
|
||||
*/
|
||||
public class BubbleBarView extends FrameLayout {
|
||||
|
||||
private static final String TAG = BubbleBarView.class.getSimpleName();
|
||||
|
||||
// TODO: (b/273594744) calculate the amount of space we have and base the max on that
|
||||
// if it's smaller than 5.
|
||||
// TODO: (b/273594744) calculate the amount of space we have and base the max on
|
||||
// that
|
||||
// if it's smaller than 5.
|
||||
private static final int MAX_BUBBLES = 5;
|
||||
private static final int ARROW_POSITION_ANIMATION_DURATION_MS = 200;
|
||||
private static final int WIDTH_ANIMATION_DURATION_MS = 200;
|
||||
|
||||
private final TaskbarActivityContext mActivityContext;
|
||||
private final BubbleBarBackground mBubbleBarBackground;
|
||||
|
||||
// The current bounds of all the bubble bar.
|
||||
/**
|
||||
* The current bounds of all the bubble bar. Note that these bounds may not
|
||||
* account for
|
||||
* translation. The bounds should be retrieved using
|
||||
* {@link #getBubbleBarBounds()} which
|
||||
* updates the bounds and accounts for translation.
|
||||
*/
|
||||
private final Rect mBubbleBarBounds = new Rect();
|
||||
// The amount the bubbles overlap when they are stacked in the bubble bar
|
||||
private final float mIconOverlapAmount;
|
||||
@@ -89,13 +108,28 @@ public class BubbleBarView extends FrameLayout {
|
||||
private View.OnClickListener mOnClickListener;
|
||||
|
||||
private final Rect mTempRect = new Rect();
|
||||
private float mRelativePivotX = 1f;
|
||||
private float mRelativePivotY = 1f;
|
||||
|
||||
// We don't reorder the bubbles when they are expanded as it could be jarring for the user
|
||||
// this runnable will be populated with any reordering of the bubbles that should be applied
|
||||
// An animator that represents the expansion state of the bubble bar, where 0
|
||||
// corresponds to the
|
||||
// collapsed state and 1 to the fully expanded state.
|
||||
private final ValueAnimator mWidthAnimator = ValueAnimator.ofFloat(0, 1);
|
||||
|
||||
// We don't reorder the bubbles when they are expanded as it could be jarring
|
||||
// for the user
|
||||
// this runnable will be populated with any reordering of the bubbles that
|
||||
// should be applied
|
||||
// once they are collapsed.
|
||||
@Nullable
|
||||
private Runnable mReorderRunnable;
|
||||
|
||||
@Nullable
|
||||
private Consumer<String> mUpdateSelectedBubbleAfterCollapse;
|
||||
|
||||
@Nullable
|
||||
private BubbleView mDraggedBubbleView;
|
||||
|
||||
public BubbleBarView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@@ -110,7 +144,7 @@ public class BubbleBarView extends FrameLayout {
|
||||
|
||||
public BubbleBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
mActivityContext = ActivityContext.lookupContext(context);
|
||||
TaskbarActivityContext activityContext = ActivityContext.lookupContext(context);
|
||||
|
||||
mIconOverlapAmount = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_overlap);
|
||||
mIconSpacing = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
|
||||
@@ -118,9 +152,46 @@ public class BubbleBarView extends FrameLayout {
|
||||
mBubbleElevation = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_elevation);
|
||||
setClipToPadding(false);
|
||||
|
||||
mBubbleBarBackground = new BubbleBarBackground(mActivityContext,
|
||||
mBubbleBarBackground = new BubbleBarBackground(activityContext,
|
||||
getResources().getDimensionPixelSize(R.dimen.bubblebar_size));
|
||||
setBackgroundDrawable(mBubbleBarBackground);
|
||||
|
||||
mWidthAnimator.setDuration(WIDTH_ANIMATION_DURATION_MS);
|
||||
mWidthAnimator.addUpdateListener(animation -> {
|
||||
updateChildrenRenderNodeProperties();
|
||||
invalidate();
|
||||
});
|
||||
mWidthAnimator.addListener(new Animator.AnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mBubbleBarBackground.showArrow(mIsBarExpanded);
|
||||
if (!mIsBarExpanded && mReorderRunnable != null) {
|
||||
mReorderRunnable.run();
|
||||
mReorderRunnable = null;
|
||||
}
|
||||
// If the bar was just collapsed and the overflow was the last bubble that was
|
||||
// selected, set the first bubble as selected.
|
||||
if (!mIsBarExpanded && mUpdateSelectedBubbleAfterCollapse != null
|
||||
&& mSelectedBubbleView.getBubble() instanceof BubbleBarOverflow) {
|
||||
BubbleView firstBubble = (BubbleView) getChildAt(0);
|
||||
mUpdateSelectedBubbleAfterCollapse.accept(firstBubble.getBubble().getKey());
|
||||
}
|
||||
updateWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mBubbleBarBackground.showArrow(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -131,63 +202,167 @@ public class BubbleBarView extends FrameLayout {
|
||||
mBubbleBarBounds.right = right;
|
||||
mBubbleBarBounds.bottom = bottom;
|
||||
|
||||
// The bubble bar handle is aligned to the bottom edge of the screen so scale towards that.
|
||||
setPivotX(getWidth());
|
||||
setPivotY(getHeight());
|
||||
// The bubble bar handle is aligned according to the relative pivot,
|
||||
// by default it's aligned to the bottom edge of the screen so scale towards
|
||||
// that
|
||||
setPivotX(mRelativePivotX * getWidth());
|
||||
setPivotY(mRelativePivotY * getHeight());
|
||||
|
||||
// Position the views
|
||||
updateChildrenRenderNodeProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bounds of the bubble bar.
|
||||
* Updates the bounds with translation that may have been applied and returns
|
||||
* the result.
|
||||
*/
|
||||
public Rect getBubbleBarBounds() {
|
||||
mBubbleBarBounds.top = getTop() + (int) getTranslationY();
|
||||
mBubbleBarBounds.bottom = getBottom() + (int) getTranslationY();
|
||||
return mBubbleBarBounds;
|
||||
}
|
||||
|
||||
// TODO: (b/273592694) animate it
|
||||
@Override
|
||||
public void addView(View child, int index, ViewGroup.LayoutParams params) {
|
||||
if (getChildCount() + 1 > MAX_BUBBLES) {
|
||||
removeViewInLayout(getChildAt(getChildCount() - 1));
|
||||
}
|
||||
super.addView(child, index, params);
|
||||
/**
|
||||
* Set bubble bar relative pivot value for X and Y, applied as a fraction of
|
||||
* view width/height
|
||||
* respectively. If the value is not in range of 0 to 1 it will be normalized.
|
||||
*
|
||||
* @param x relative X pivot value in range 0..1
|
||||
* @param y relative Y pivot value in range 0..1
|
||||
*/
|
||||
public void setRelativePivot(float x, float y) {
|
||||
mRelativePivotX = Float.max(Float.min(x, 1), 0);
|
||||
mRelativePivotY = Float.max(Float.min(y, 1), 0);
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the z order, positions, and badge visibility of the bubble views in the bar based
|
||||
* Get current relative pivot for X axis
|
||||
*/
|
||||
public float getRelativePivotX() {
|
||||
return mRelativePivotX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current relative pivot for Y axis
|
||||
*/
|
||||
public float getRelativePivotY() {
|
||||
return mRelativePivotY;
|
||||
}
|
||||
|
||||
// TODO: (b/280605790) animate it
|
||||
@Override
|
||||
public void addView(View child, int index, ViewGroup.LayoutParams params) {
|
||||
if (getChildCount() + 1 > MAX_BUBBLES) {
|
||||
// the last child view is the overflow bubble and we shouldn't remove that.
|
||||
// remove the
|
||||
// second to last child view.
|
||||
removeViewInLayout(getChildAt(getChildCount() - 2));
|
||||
}
|
||||
super.addView(child, index, params);
|
||||
updateWidth();
|
||||
}
|
||||
|
||||
// TODO: (b/283309949) animate it
|
||||
@Override
|
||||
public void removeView(View view) {
|
||||
super.removeView(view);
|
||||
updateWidth();
|
||||
}
|
||||
|
||||
private void updateWidth() {
|
||||
LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
|
||||
lp.width = (int) (mIsBarExpanded ? expandedWidth() : collapsedWidth());
|
||||
setLayoutParams(lp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the horizontal margin between the bubble bar and the edge of the
|
||||
* screen.
|
||||
*/
|
||||
int getHorizontalMargin() {
|
||||
LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
|
||||
return lp.getMarginEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the z order, positions, and badge visibility of the bubble views in
|
||||
* the bar based
|
||||
* on the expanded state.
|
||||
*/
|
||||
// TODO: (b/273592694) animate it
|
||||
private void updateChildrenRenderNodeProperties() {
|
||||
final float widthState = (float) mWidthAnimator.getAnimatedValue();
|
||||
final float currentWidth = getWidth();
|
||||
final float expandedWidth = expandedWidth();
|
||||
final float collapsedWidth = collapsedWidth();
|
||||
int bubbleCount = getChildCount();
|
||||
final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f;
|
||||
final boolean animate = getVisibility() == VISIBLE;
|
||||
for (int i = 0; i < bubbleCount; i++) {
|
||||
BubbleView bv = (BubbleView) getChildAt(i);
|
||||
bv.setTranslationY(ty);
|
||||
|
||||
// the position of the bubble when the bar is fully expanded
|
||||
final float expandedX = i * (mIconSize + mIconSpacing);
|
||||
// the position of the bubble when the bar is fully collapsed
|
||||
final float collapsedX = i == 0 ? 0 : mIconOverlapAmount;
|
||||
|
||||
if (mIsBarExpanded) {
|
||||
final float tx = i * (mIconSize + mIconSpacing);
|
||||
bv.setTranslationX(tx);
|
||||
bv.setZ(0);
|
||||
bv.showBadge();
|
||||
// where the bubble will end up when the animation ends
|
||||
final float targetX = currentWidth - expandedWidth + expandedX;
|
||||
bv.setTranslationX(widthState * (targetX - collapsedX) + collapsedX);
|
||||
// if we're fully expanded, set the z level to 0 or to bubble elevation if
|
||||
// dragged
|
||||
if (widthState == 1f) {
|
||||
bv.setZ(bv == mDraggedBubbleView ? mBubbleElevation : 0);
|
||||
}
|
||||
// When we're expanded, we're not stacked so we're not behind the stack
|
||||
bv.setBehindStack(false, animate);
|
||||
bv.setAlpha(1);
|
||||
} else {
|
||||
final float targetX = currentWidth - collapsedWidth + collapsedX;
|
||||
bv.setTranslationX(widthState * (expandedX - targetX) + targetX);
|
||||
bv.setZ((MAX_BUBBLES * mBubbleElevation) - i);
|
||||
bv.setTranslationX(i * mIconOverlapAmount);
|
||||
if (i > 0) {
|
||||
bv.hideBadge();
|
||||
} else {
|
||||
bv.showBadge();
|
||||
// If we're not the first bubble we're behind the stack
|
||||
bv.setBehindStack(i > 0, animate);
|
||||
// If we're fully collapsed, hide all bubbles except for the first 2. If there
|
||||
// are
|
||||
// only 2 bubbles, hide the second bubble as well because it's the overflow.
|
||||
if (widthState == 0) {
|
||||
if (i > 1) {
|
||||
bv.setAlpha(0);
|
||||
} else if (i == 1 && bubbleCount == 2) {
|
||||
bv.setAlpha(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update the arrow position
|
||||
final float collapsedArrowPosition = arrowPositionForSelectedWhenCollapsed();
|
||||
final float expandedArrowPosition = arrowPositionForSelectedWhenExpanded();
|
||||
final float interpolatedWidth = widthState * (expandedWidth - collapsedWidth) + collapsedWidth;
|
||||
if (mIsBarExpanded) {
|
||||
// when the bar is expanding, the selected bubble is always the first, so the
|
||||
// arrow
|
||||
// always shifts with the interpolated width.
|
||||
final float arrowPosition = currentWidth - interpolatedWidth + collapsedArrowPosition;
|
||||
mBubbleBarBackground.setArrowPosition(arrowPosition);
|
||||
} else {
|
||||
final float targetPosition = currentWidth - collapsedWidth + collapsedArrowPosition;
|
||||
final float arrowPosition = targetPosition + widthState * (expandedArrowPosition - targetPosition);
|
||||
mBubbleBarBackground.setArrowPosition(arrowPosition);
|
||||
}
|
||||
|
||||
mBubbleBarBackground.setArrowAlpha((int) (255 * widthState));
|
||||
mBubbleBarBackground.setWidth(interpolatedWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorders the views to match the provided list.
|
||||
*/
|
||||
public void reorder(List<BubbleView> viewOrder) {
|
||||
if (isExpanded()) {
|
||||
if (isExpanded() || mWidthAnimator.isRunning()) {
|
||||
mReorderRunnable = () -> doReorder(viewOrder);
|
||||
} else {
|
||||
doReorder(viewOrder);
|
||||
@@ -199,7 +374,10 @@ public class BubbleBarView extends FrameLayout {
|
||||
if (!isExpanded()) {
|
||||
for (int i = 0; i < viewOrder.size(); i++) {
|
||||
View child = viewOrder.get(i);
|
||||
if (child != null) {
|
||||
// this child view may have already been removed so verify that it still exists
|
||||
// before reordering it, otherwise it will be re-added.
|
||||
int indexOfChild = indexOfChild(child);
|
||||
if (child != null && indexOfChild >= 0) {
|
||||
removeViewInLayout(child);
|
||||
addViewInLayout(child, i, child.getLayoutParams());
|
||||
}
|
||||
@@ -208,6 +386,11 @@ public class BubbleBarView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
public void setUpdateSelectedBubbleAfterCollapse(
|
||||
Consumer<String> updateSelectedBubbleAfterCollapse) {
|
||||
mUpdateSelectedBubbleAfterCollapse = updateSelectedBubbleAfterCollapse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets which bubble view should be shown as selected.
|
||||
*/
|
||||
@@ -216,11 +399,22 @@ public class BubbleBarView extends FrameLayout {
|
||||
updateArrowForSelected(/* shouldAnimate= */ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dragged bubble view to correctly apply Z order. Dragged view should
|
||||
* appear on top
|
||||
*/
|
||||
public void setDraggedBubble(@Nullable BubbleView view) {
|
||||
mDraggedBubbleView = view;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the arrow position to match the selected bubble.
|
||||
*
|
||||
* @param shouldAnimate whether or not to animate the arrow. If the bar was just expanded, this
|
||||
* should be set to {@code false}. Otherwise set this to {@code true}.
|
||||
* @param shouldAnimate whether or not to animate the arrow. If the bar was just
|
||||
* expanded, this
|
||||
* should be set to {@code false}. Otherwise set this to
|
||||
* {@code true}.
|
||||
*/
|
||||
private void updateArrowForSelected(boolean shouldAnimate) {
|
||||
if (mSelectedBubbleView == null) {
|
||||
@@ -228,7 +422,8 @@ public class BubbleBarView extends FrameLayout {
|
||||
return;
|
||||
}
|
||||
final int index = indexOfChild(mSelectedBubbleView);
|
||||
// Find the center of the bubble when it's expanded, set the arrow position to it.
|
||||
// Find the center of the bubble when it's expanded, set the arrow position to
|
||||
// it.
|
||||
final float tx = getPaddingStart() + index * (mIconSize + mIconSpacing) + mIconSize / 2f;
|
||||
|
||||
if (shouldAnimate) {
|
||||
@@ -247,6 +442,16 @@ public class BubbleBarView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private float arrowPositionForSelectedWhenExpanded() {
|
||||
final int index = indexOfChild(mSelectedBubbleView);
|
||||
return getPaddingStart() + index * (mIconSize + mIconSpacing) + mIconSize / 2f;
|
||||
}
|
||||
|
||||
private float arrowPositionForSelectedWhenCollapsed() {
|
||||
final int index = indexOfChild(mSelectedBubbleView);
|
||||
return getPaddingStart() + index * (mIconOverlapAmount) + mIconSize / 2f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClickListener(View.OnClickListener listener) {
|
||||
mOnClickListener = listener;
|
||||
@@ -254,8 +459,10 @@ public class BubbleBarView extends FrameLayout {
|
||||
}
|
||||
|
||||
/**
|
||||
* The click listener used for the bubble view gets added / removed depending on whether
|
||||
* the bar is expanded or collapsed, this updates whether the listener is set based on state.
|
||||
* The click listener used for the bubble view gets added / removed depending on
|
||||
* whether
|
||||
* the bar is expanded or collapsed, this updates whether the listener is set
|
||||
* based on state.
|
||||
*/
|
||||
private void setOrUnsetClickListener() {
|
||||
super.setOnClickListener(mIsBarExpanded ? null : mOnClickListener);
|
||||
@@ -264,18 +471,16 @@ public class BubbleBarView extends FrameLayout {
|
||||
/**
|
||||
* Sets whether the bubble bar is expanded or collapsed.
|
||||
*/
|
||||
// TODO: (b/273592694) animate it
|
||||
public void setExpanded(boolean isBarExpanded) {
|
||||
if (mIsBarExpanded != isBarExpanded) {
|
||||
mIsBarExpanded = isBarExpanded;
|
||||
updateArrowForSelected(/* shouldAnimate= */ false);
|
||||
setOrUnsetClickListener();
|
||||
if (!isBarExpanded && mReorderRunnable != null) {
|
||||
mReorderRunnable.run();
|
||||
mReorderRunnable = null;
|
||||
if (isBarExpanded) {
|
||||
mWidthAnimator.start();
|
||||
} else {
|
||||
mWidthAnimator.reverse();
|
||||
}
|
||||
mBubbleBarBackground.showArrow(mIsBarExpanded);
|
||||
requestLayout(); // trigger layout to reposition views & update size for expansion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,23 +491,27 @@ public class BubbleBarView extends FrameLayout {
|
||||
return mIsBarExpanded;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
private float expandedWidth() {
|
||||
final int childCount = getChildCount();
|
||||
final float iconWidth = mIsBarExpanded
|
||||
? (childCount * (mIconSize + mIconSpacing))
|
||||
: mIconSize + ((childCount - 1) * mIconOverlapAmount);
|
||||
final int totalWidth = (int) iconWidth + getPaddingStart() + getPaddingEnd();
|
||||
setMeasuredDimension(totalWidth, MeasureSpec.getSize(heightMeasureSpec));
|
||||
final int horizontalPadding = getPaddingStart() + getPaddingEnd();
|
||||
return childCount * (mIconSize + mIconSpacing) + horizontalPadding;
|
||||
}
|
||||
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View child = getChildAt(i);
|
||||
measureChild(child, (int) mIconSize, (int) mIconSize);
|
||||
}
|
||||
private float collapsedWidth() {
|
||||
final int childCount = getChildCount();
|
||||
final int horizontalPadding = getPaddingStart() + getPaddingEnd();
|
||||
// If there are more than 2 bubbles, the first 2 should be visible when
|
||||
// collapsed.
|
||||
// Otherwise just the first bubble should be visible because we don't show the
|
||||
// overflow.
|
||||
return childCount > 2
|
||||
? mIconSize + mIconOverlapAmount + horizontalPadding
|
||||
: mIconSize + horizontalPadding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given MotionEvent, *in screen coordinates*, is within bubble bar
|
||||
* Returns whether the given MotionEvent, *in screen coordinates*, is within
|
||||
* bubble bar
|
||||
* touch bounds.
|
||||
*/
|
||||
public boolean isEventOverAnyItem(MotionEvent ev) {
|
||||
|
||||
@@ -24,19 +24,25 @@ import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
import com.android.launcher3.taskbar.TaskbarControllers;
|
||||
import com.android.launcher3.taskbar.TaskbarInsetsController;
|
||||
import com.android.launcher3.taskbar.TaskbarStashController;
|
||||
import com.android.launcher3.util.MultiPropertyFactory;
|
||||
import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Controller for {@link BubbleBarView}. Manages the visibility of the bubble bar as well as
|
||||
* Controller for {@link BubbleBarView}. Manages the visibility of the bubble
|
||||
* bar as well as
|
||||
* responding to changes in bubble state provided by BubbleBarController.
|
||||
*/
|
||||
public class BubbleBarViewController {
|
||||
@@ -51,10 +57,14 @@ public class BubbleBarViewController {
|
||||
// Initialized in init.
|
||||
private BubbleStashController mBubbleStashController;
|
||||
private BubbleBarController mBubbleBarController;
|
||||
private BubbleDragController mBubbleDragController;
|
||||
private TaskbarStashController mTaskbarStashController;
|
||||
private TaskbarInsetsController mTaskbarInsetsController;
|
||||
private View.OnClickListener mBubbleClickListener;
|
||||
private View.OnClickListener mBubbleBarClickListener;
|
||||
|
||||
// These are exposed to {@link BubbleStashController} to animate for stashing/un-stashing
|
||||
// These are exposed to {@link BubbleStashController} to animate for
|
||||
// stashing/un-stashing
|
||||
private final MultiValueAlpha mBubbleBarAlpha;
|
||||
private final AnimatedFloat mBubbleBarScale = new AnimatedFloat(this::updateScale);
|
||||
private final AnimatedFloat mBubbleBarTranslationY = new AnimatedFloat(
|
||||
@@ -80,20 +90,24 @@ public class BubbleBarViewController {
|
||||
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
|
||||
mBubbleStashController = bubbleControllers.bubbleStashController;
|
||||
mBubbleBarController = bubbleControllers.bubbleBarController;
|
||||
mBubbleDragController = bubbleControllers.bubbleDragController;
|
||||
mTaskbarStashController = controllers.taskbarStashController;
|
||||
mTaskbarInsetsController = controllers.taskbarInsetsController;
|
||||
|
||||
mActivity.addOnDeviceProfileChangeListener(dp ->
|
||||
mBarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarHeight
|
||||
);
|
||||
mActivity.addOnDeviceProfileChangeListener(
|
||||
dp -> mBarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarHeight);
|
||||
mBarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarHeight;
|
||||
mBubbleBarScale.updateValue(1f);
|
||||
mBubbleClickListener = v -> onBubbleClicked(v);
|
||||
mBubbleBarClickListener = v -> setExpanded(true);
|
||||
mBubbleDragController.setupBubbleBarView(mBarView);
|
||||
mBarView.setOnClickListener(mBubbleBarClickListener);
|
||||
// TODO: when barView layout changes tell taskbarInsetsController the insets have changed.
|
||||
mBarView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> mTaskbarInsetsController
|
||||
.onTaskbarOrBubblebarWindowHeightOrInsetsChanged());
|
||||
}
|
||||
|
||||
private void onBubbleClicked(View v) {
|
||||
BubbleBarBubble bubble = ((BubbleView) v).getBubble();
|
||||
BubbleBarItem bubble = ((BubbleView) v).getBubble();
|
||||
if (bubble == null) {
|
||||
Log.e(TAG, "bubble click listener, bubble was null");
|
||||
}
|
||||
@@ -103,14 +117,13 @@ public class BubbleBarViewController {
|
||||
setExpanded(false);
|
||||
mBubbleStashController.stashBubbleBar();
|
||||
} else {
|
||||
mBubbleBarController.setSelectedBubble(bubble);
|
||||
mSystemUiProxy.showBubble(bubble.getKey(),
|
||||
mBubbleStashController.isBubblesShowingOnHome());
|
||||
mBubbleBarController.showAndSelectBubble(bubble);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// The below animators are exposed to BubbleStashController so it can manage the stashing
|
||||
// The below animators are exposed to BubbleStashController so it can manage the
|
||||
// stashing
|
||||
// animation.
|
||||
//
|
||||
|
||||
@@ -133,6 +146,11 @@ public class BubbleBarViewController {
|
||||
return mBarView.getVisibility() == VISIBLE;
|
||||
}
|
||||
|
||||
/** Whether the bubble bar has bubbles. */
|
||||
public boolean hasBubbles() {
|
||||
return mBubbleBarController.getSelectedBubbleKey() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The bounds of the bubble bar.
|
||||
*/
|
||||
@@ -140,9 +158,16 @@ public class BubbleBarViewController {
|
||||
return mBarView.getBubbleBarBounds();
|
||||
}
|
||||
|
||||
/** The horizontal margin of the bubble bar from the edge of the screen. */
|
||||
public int getHorizontalMargin() {
|
||||
return mBarView.getHorizontalMargin();
|
||||
}
|
||||
|
||||
/**
|
||||
* When the bubble bar is not stashed, it can be collapsed (the icons are in a stack) or
|
||||
* expanded (the icons are in a row). This indicates whether the bubble bar is expanded.
|
||||
* When the bubble bar is not stashed, it can be collapsed (the icons are in a
|
||||
* stack) or
|
||||
* expanded (the icons are in a row). This indicates whether the bubble bar is
|
||||
* expanded.
|
||||
*/
|
||||
public boolean isExpanded() {
|
||||
return mBarView.isExpanded();
|
||||
@@ -177,7 +202,17 @@ public class BubbleBarViewController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the bubble bar should be hidden due to SysUI state (e.g. on lockscreen).
|
||||
* Sets a callback that updates the selected bubble after the bubble bar
|
||||
* collapses.
|
||||
*/
|
||||
public void setUpdateSelectedBubbleAfterCollapse(
|
||||
Consumer<String> updateSelectedBubbleAfterCollapse) {
|
||||
mBarView.setUpdateSelectedBubbleAfterCollapse(updateSelectedBubbleAfterCollapse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the bubble bar should be hidden due to SysUI state (e.g. on
|
||||
* lockscreen).
|
||||
*/
|
||||
public void setHiddenForSysui(boolean hidden) {
|
||||
if (mHiddenForSysui != hidden) {
|
||||
@@ -188,10 +223,12 @@ public class BubbleBarViewController {
|
||||
|
||||
// TODO: (b/273592694) animate it
|
||||
private void updateVisibilityForStateChange() {
|
||||
if (!mHiddenForSysui && !mBubbleStashController.isStashed() && !mHiddenForNoBubbles) {
|
||||
if (!mHiddenForSysui && !mHiddenForNoBubbles) {
|
||||
mBarView.setVisibility(VISIBLE);
|
||||
} else {
|
||||
mBarView.setVisibility(INVISIBLE);
|
||||
mBarView.setAlpha(0);
|
||||
mBarView.setExpanded(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +265,7 @@ public class BubbleBarViewController {
|
||||
/**
|
||||
* Removes the provided bubble from the bubble bar.
|
||||
*/
|
||||
public void removeBubble(BubbleBarBubble b) {
|
||||
public void removeBubble(BubbleBarItem b) {
|
||||
if (b != null) {
|
||||
mBarView.removeView(b.getView());
|
||||
} else {
|
||||
@@ -239,10 +276,11 @@ public class BubbleBarViewController {
|
||||
/**
|
||||
* Adds the provided bubble to the bubble bar.
|
||||
*/
|
||||
public void addBubble(BubbleBarBubble b) {
|
||||
public void addBubble(BubbleBarItem b) {
|
||||
if (b != null) {
|
||||
mBarView.addView(b.getView(), 0, new FrameLayout.LayoutParams(mIconSize, mIconSize));
|
||||
b.getView().setOnClickListener(mBubbleClickListener);
|
||||
mBubbleDragController.setupBubbleView(b.getView());
|
||||
} else {
|
||||
Log.w(TAG, "addBubble, bubble was null!");
|
||||
}
|
||||
@@ -260,14 +298,17 @@ public class BubbleBarViewController {
|
||||
/**
|
||||
* Updates the selected bubble.
|
||||
*/
|
||||
public void updateSelectedBubble(BubbleBarBubble newlySelected) {
|
||||
public void updateSelectedBubble(BubbleBarItem newlySelected) {
|
||||
mBarView.setSelectedBubble(newlySelected.getView());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the bubble bar should be expanded (not unstashed, but have the contents
|
||||
* within it expanded). This method notifies SystemUI that the bubble bar is expanded and
|
||||
* showing a selected bubble. This method should ONLY be called from UI events originating
|
||||
* Sets whether the bubble bar should be expanded (not unstashed, but have the
|
||||
* contents
|
||||
* within it expanded). This method notifies SystemUI that the bubble bar is
|
||||
* expanded and
|
||||
* showing a selected bubble. This method should ONLY be called from UI events
|
||||
* originating
|
||||
* from Launcher.
|
||||
*/
|
||||
public void setExpanded(boolean isExpanded) {
|
||||
@@ -276,20 +317,16 @@ public class BubbleBarViewController {
|
||||
if (!isExpanded) {
|
||||
mSystemUiProxy.collapseBubbles();
|
||||
} else {
|
||||
final String selectedKey = mBubbleBarController.getSelectedBubbleKey();
|
||||
if (selectedKey != null) {
|
||||
mSystemUiProxy.showBubble(selectedKey,
|
||||
mBubbleStashController.isBubblesShowingOnHome());
|
||||
} else {
|
||||
Log.w(TAG, "trying to expand bubbles when there isn't one selected");
|
||||
}
|
||||
// TODO: Tell taskbar stash controller to stash without bubbles following
|
||||
mBubbleBarController.showSelectedBubble();
|
||||
mTaskbarStashController.updateAndAnimateTransientTaskbar(true /* stash */,
|
||||
false /* shouldBubblesFollow */);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the bubble bar should be expanded. This method is used in response to UI events
|
||||
* Sets whether the bubble bar should be expanded. This method is used in
|
||||
* response to UI events
|
||||
* from SystemUI.
|
||||
*/
|
||||
public void setExpandedFromSysui(boolean isExpanded) {
|
||||
@@ -299,4 +336,51 @@ public class BubbleBarViewController {
|
||||
mBubbleStashController.showBubbleBar(true /* expand the bubbles */);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the dragged bubble view in the bubble bar view, and notifies SystemUI
|
||||
* that a bubble is being dragged to dismiss.
|
||||
*
|
||||
* @param bubbleView dragged bubble view
|
||||
*/
|
||||
public void onDragStart(@NonNull BubbleView bubbleView) {
|
||||
if (bubbleView.getBubble() == null)
|
||||
return;
|
||||
mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ true);
|
||||
mBarView.setDraggedBubble(bubbleView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies SystemUI to expand the selected bubble when the bubble is released.
|
||||
*
|
||||
* @param bubbleView dragged bubble view
|
||||
*/
|
||||
public void onDragRelease(@NonNull BubbleView bubbleView) {
|
||||
if (bubbleView.getBubble() == null)
|
||||
return;
|
||||
mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the dragged bubble view in the bubble bar view
|
||||
*/
|
||||
public void onDragEnd() {
|
||||
mBarView.setDraggedBubble(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when bubble was dragged into the dismiss target. Notifies System
|
||||
*
|
||||
* @param bubble dismissed bubble item
|
||||
*/
|
||||
public void onDismissBubbleWhileDragging(@NonNull BubbleBarItem bubble) {
|
||||
mSystemUiProxy.removeBubble(bubble.getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when bubble stack was dragged into the dismiss target
|
||||
*/
|
||||
public void onDismissAllBubblesWhileDragging() {
|
||||
mSystemUiProxy.removeAllBubbles();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,28 +27,36 @@ public class BubbleControllers {
|
||||
public final BubbleBarViewController bubbleBarViewController;
|
||||
public final BubbleStashController bubbleStashController;
|
||||
public final BubbleStashedHandleViewController bubbleStashedHandleViewController;
|
||||
public final BubbleDragController bubbleDragController;
|
||||
public final BubbleDismissController bubbleDismissController;
|
||||
|
||||
private final RunnableList mPostInitRunnables = new RunnableList();
|
||||
|
||||
/**
|
||||
* Want to add a new controller? Don't forget to:
|
||||
* * Call init
|
||||
* * Call onDestroy
|
||||
* * Call init
|
||||
* * Call onDestroy
|
||||
*/
|
||||
public BubbleControllers(
|
||||
BubbleBarController bubbleBarController,
|
||||
BubbleBarViewController bubbleBarViewController,
|
||||
BubbleStashController bubbleStashController,
|
||||
BubbleStashedHandleViewController bubbleStashedHandleViewController) {
|
||||
BubbleStashedHandleViewController bubbleStashedHandleViewController,
|
||||
BubbleDragController bubbleDragController,
|
||||
BubbleDismissController bubbleDismissController) {
|
||||
this.bubbleBarController = bubbleBarController;
|
||||
this.bubbleBarViewController = bubbleBarViewController;
|
||||
this.bubbleStashController = bubbleStashController;
|
||||
this.bubbleStashedHandleViewController = bubbleStashedHandleViewController;
|
||||
this.bubbleDragController = bubbleDragController;
|
||||
this.bubbleDismissController = bubbleDismissController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all controllers. Note that controllers can now reference each other through this
|
||||
* BubbleControllers instance, but should be careful to only access things that were created
|
||||
* Initializes all controllers. Note that controllers can now reference each
|
||||
* other through this
|
||||
* BubbleControllers 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(TaskbarControllers taskbarControllers) {
|
||||
@@ -56,14 +64,19 @@ public class BubbleControllers {
|
||||
bubbleBarViewController.init(taskbarControllers, this);
|
||||
bubbleStashedHandleViewController.init(taskbarControllers, this);
|
||||
bubbleStashController.init(taskbarControllers, this);
|
||||
bubbleDragController.init(/* bubbleControllers = */ this);
|
||||
bubbleDismissController.init(/* bubbleControllers = */ this);
|
||||
|
||||
mPostInitRunnables.executeAllAndDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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().
|
||||
* 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 runnable) {
|
||||
// If this has been executed in init, it automatically runs adds to it.
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.bubbles;
|
||||
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
import com.android.launcher3.taskbar.TaskbarDragLayer;
|
||||
|
||||
import app.lawnchair.bubbles.DismissView;
|
||||
import app.lawnchair.common.MagnetizedObject;
|
||||
|
||||
|
||||
/**
|
||||
* Controls dismiss view presentation for the bubble bar dismiss functionality.
|
||||
* Provides the dragged view snapping to the target dismiss area and animates it.
|
||||
* When the dragged bubble/bubble stack is released inside of the target area, it gets dismissed.
|
||||
*
|
||||
* @see BubbleDragController
|
||||
*/
|
||||
public class BubbleDismissController {
|
||||
private static final String TAG = BubbleDismissController.class.getSimpleName();
|
||||
private static final float FLING_TO_DISMISS_MIN_VELOCITY = 6000f;
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final TaskbarDragLayer mDragLayer;
|
||||
@Nullable
|
||||
private BubbleBarViewController mBubbleBarViewController;
|
||||
|
||||
// Dismiss view that's attached to drag layer. It consists of the scrim view and the circular
|
||||
// dismiss view used as a dismiss target.
|
||||
@Nullable
|
||||
private DismissView mDismissView;
|
||||
|
||||
// The currently magnetized object, which is being dragged and will be attracted to the magnetic
|
||||
// dismiss target. This is either the stack itself, or an individual bubble.
|
||||
@Nullable
|
||||
private MagnetizedObject<View> mMagnetizedObject;
|
||||
|
||||
// The MagneticTarget instance for our circular dismiss view. This is added to the
|
||||
// MagnetizedObject instances for the stack and any dragged-out bubbles.
|
||||
@Nullable
|
||||
private MagnetizedObject.MagneticTarget mMagneticTarget;
|
||||
|
||||
// The bubble drag animator that synchronizes bubble drag and dismiss view animations
|
||||
// A new instance is provided when the dismiss view is setup
|
||||
@Nullable
|
||||
private BubbleDragAnimator mAnimator;
|
||||
|
||||
public BubbleDismissController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
|
||||
mActivity = activity;
|
||||
mDragLayer = dragLayer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes dependencies when bubble controllers are created.
|
||||
* 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(@NonNull BubbleControllers bubbleControllers) {
|
||||
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the dismiss view and magnetized object that will be attracted to magnetic target.
|
||||
* Should be called before handling events or showing/hiding dismiss view.
|
||||
*
|
||||
* @param magnetizedView the view to be pulled into target dismiss area
|
||||
* @param animator the bubble animator to be used for the magnetized view, it syncs bubble
|
||||
* dragging and dismiss animations with the dismiss view provided.
|
||||
*/
|
||||
public void setupDismissView(@NonNull View magnetizedView,
|
||||
@NonNull BubbleDragAnimator animator) {
|
||||
setupDismissView();
|
||||
setupMagnetizedObject(magnetizedView);
|
||||
if (mDismissView != null) {
|
||||
animator.setDismissView(mDismissView);
|
||||
mAnimator = animator;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the touch event and pass it to the magnetized object.
|
||||
* It should be called after {@code setupDismissView}
|
||||
*/
|
||||
public boolean handleTouchEvent(@NonNull MotionEvent event) {
|
||||
return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dismiss view with animation
|
||||
* It should be called after {@code setupDismissView}
|
||||
*/
|
||||
public void showDismissView() {
|
||||
if (mDismissView == null) return;
|
||||
mDismissView.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide dismiss view with animation
|
||||
* It should be called after {@code setupDismissView}
|
||||
*/
|
||||
public void hideDismissView() {
|
||||
if (mDismissView == null) return;
|
||||
mDismissView.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss magnetized object when it's released in the dismiss target area
|
||||
*/
|
||||
private void dismissMagnetizedObject() {
|
||||
if (mMagnetizedObject == null || mBubbleBarViewController == null) return;
|
||||
if (mMagnetizedObject.getUnderlyingObject() instanceof BubbleView) {
|
||||
BubbleView bubbleView = (BubbleView) mMagnetizedObject.getUnderlyingObject();
|
||||
if (bubbleView.getBubble() != null) {
|
||||
mBubbleBarViewController.onDismissBubbleWhileDragging(bubbleView.getBubble());
|
||||
}
|
||||
} else if (mMagnetizedObject.getUnderlyingObject() instanceof BubbleBarView) {
|
||||
mBubbleBarViewController.onDismissAllBubblesWhileDragging();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupDismissView() {
|
||||
if (mDismissView != null) return;
|
||||
mDismissView = new DismissView(mActivity.getApplicationContext());
|
||||
BubbleDismissViewUtils.setup(mDismissView);
|
||||
mDragLayer.addView(mDismissView, /* index = */ 0,
|
||||
new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
|
||||
mDismissView.setElevation(mDismissView.getResources().getDimensionPixelSize(
|
||||
R.dimen.bubblebar_elevation));
|
||||
setupMagneticTarget(mDismissView.getCircle());
|
||||
}
|
||||
|
||||
private void setupMagneticTarget(@NonNull View view) {
|
||||
int magneticFieldRadius = mActivity.getResources().getDimensionPixelSize(
|
||||
R.dimen.bubblebar_dismiss_target_size);
|
||||
mMagneticTarget = new MagnetizedObject.MagneticTarget(view, magneticFieldRadius);
|
||||
}
|
||||
|
||||
private void setupMagnetizedObject(@NonNull View magnetizedView) {
|
||||
mMagnetizedObject = new MagnetizedObject<>(mActivity.getApplicationContext(),
|
||||
magnetizedView, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y) {
|
||||
@Override
|
||||
public float getWidth(@NonNull View underlyingObject) {
|
||||
return underlyingObject.getWidth() * underlyingObject.getScaleX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getHeight(@NonNull View underlyingObject) {
|
||||
return underlyingObject.getHeight() * underlyingObject.getScaleY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getLocationOnScreen(@NonNull View underlyingObject, @NonNull int[] loc) {
|
||||
underlyingObject.getLocationOnScreen(loc);
|
||||
}
|
||||
};
|
||||
|
||||
mMagnetizedObject.setHapticsEnabled(true);
|
||||
mMagnetizedObject.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
|
||||
if (mMagneticTarget != null) {
|
||||
mMagnetizedObject.addTarget(mMagneticTarget);
|
||||
} else {
|
||||
Log.e(TAG,"Requires MagneticTarget to add target to MagnetizedObject!");
|
||||
}
|
||||
mMagnetizedObject.setMagnetListener(new MagnetizedObject.MagnetListener() {
|
||||
@Override
|
||||
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
|
||||
if (mAnimator == null) return;
|
||||
mAnimator.animateDismissCaptured();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
|
||||
float velX, float velY, boolean wasFlungOut) {
|
||||
if (mAnimator == null) return;
|
||||
mAnimator.animateDismissReleased();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
|
||||
dismissMagnetizedObject();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.
|
||||
*/
|
||||
@file:JvmName("BubbleDismissViewUtils")
|
||||
|
||||
package com.android.launcher3.taskbar.bubbles
|
||||
|
||||
import app.lawnchair.bubbles.DismissView
|
||||
import com.android.launcher3.R
|
||||
|
||||
/**
|
||||
* Dismiss view is shared from WMShell. It requires setup with local resources.
|
||||
*
|
||||
* Usage:
|
||||
* - Kotlin `dismissView.setup()`
|
||||
* - Java `BubbleDismissViewUtils.setup(dismissView)`
|
||||
*/
|
||||
fun DismissView.setup() {
|
||||
setup(
|
||||
DismissView.Config(
|
||||
targetSizeResId = R.dimen.bubblebar_dismiss_target_size,
|
||||
iconSizeResId = R.dimen.bubblebar_dismiss_target_icon_size,
|
||||
bottomMarginResId = R.dimen.bubblebar_dismiss_target_bottom_margin,
|
||||
floatingGradientHeightResId = R.dimen.bubblebar_dismiss_floating_gradient_height,
|
||||
floatingGradientColorResId = R.color.system_neutral1_900,
|
||||
backgroundResId = R.drawable.bg_bubble_dismiss_circle,
|
||||
iconResId = R.drawable.ic_bubble_dismiss_white
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.bubbles;
|
||||
|
||||
import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY;
|
||||
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
|
||||
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.PointF;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.FloatPropertyCompat;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.wm.shell.common.bubbles.DismissCircleView;
|
||||
|
||||
import app.lawnchair.animation.PhysicsAnimator;
|
||||
import app.lawnchair.bubbles.DismissView;
|
||||
|
||||
/**
|
||||
* The animator performs the bubble animations while dragging and coordinates bubble and dismiss
|
||||
* view animations when it gets magnetized, released or dismissed.
|
||||
*/
|
||||
public class BubbleDragAnimator {
|
||||
private static final float SCALE_BUBBLE_FOCUSED = 1.2f;
|
||||
private static final float SCALE_BUBBLE_CAPTURED = 0.9f;
|
||||
private static final float SCALE_BUBBLE_BAR_FOCUSED = 1.1f;
|
||||
|
||||
private final PhysicsAnimator.SpringConfig mDefaultConfig =
|
||||
new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY);
|
||||
private final PhysicsAnimator.SpringConfig mTranslationConfig =
|
||||
new PhysicsAnimator.SpringConfig(STIFFNESS_MEDIUM, DAMPING_RATIO_LOW_BOUNCY);
|
||||
@NonNull
|
||||
private final View mView;
|
||||
@NonNull
|
||||
private final PhysicsAnimator<View> mBubbleAnimator;
|
||||
@Nullable
|
||||
private DismissView mDismissView;
|
||||
@Nullable
|
||||
private PhysicsAnimator<DismissCircleView> mDismissAnimator;
|
||||
private final float mBubbleFocusedScale;
|
||||
private final float mBubbleCapturedScale;
|
||||
private final float mDismissCapturedScale;
|
||||
|
||||
/**
|
||||
* Should be initialised for each dragged view
|
||||
*
|
||||
* @param view the dragged view to animate
|
||||
*/
|
||||
public BubbleDragAnimator(@NonNull View view) {
|
||||
mView = view;
|
||||
mBubbleAnimator = PhysicsAnimator.getInstance(view);
|
||||
mBubbleAnimator.setDefaultSpringConfig(mDefaultConfig);
|
||||
|
||||
Resources resources = view.getResources();
|
||||
final int collapsedSize = resources.getDimensionPixelSize(
|
||||
R.dimen.bubblebar_dismiss_target_small_size);
|
||||
final int expandedSize = resources.getDimensionPixelSize(
|
||||
R.dimen.bubblebar_dismiss_target_size);
|
||||
mDismissCapturedScale = (float) collapsedSize / expandedSize;
|
||||
|
||||
if (view instanceof BubbleBarView) {
|
||||
mBubbleFocusedScale = SCALE_BUBBLE_BAR_FOCUSED;
|
||||
mBubbleCapturedScale = mDismissCapturedScale;
|
||||
} else {
|
||||
mBubbleFocusedScale = SCALE_BUBBLE_FOCUSED;
|
||||
mBubbleCapturedScale = SCALE_BUBBLE_CAPTURED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets dismiss view to be animated alongside the dragged bubble
|
||||
*/
|
||||
public void setDismissView(@NonNull DismissView dismissView) {
|
||||
mDismissView = dismissView;
|
||||
mDismissAnimator = PhysicsAnimator.getInstance(dismissView.getCircle());
|
||||
mDismissAnimator.setDefaultSpringConfig(mDefaultConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the focused state of the bubble when the dragging starts
|
||||
*/
|
||||
public void animateFocused() {
|
||||
mBubbleAnimator.cancel();
|
||||
mBubbleAnimator
|
||||
.spring(DynamicAnimation.SCALE_X, mBubbleFocusedScale)
|
||||
.spring(DynamicAnimation.SCALE_Y, mBubbleFocusedScale)
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the dragged bubble movement back to the initial position.
|
||||
*
|
||||
* @param initialPosition the position to animate to
|
||||
* @param velocity the initial velocity to use for the spring animation
|
||||
* @param endActions gets called when the animation completes or gets cancelled
|
||||
*/
|
||||
public void animateToInitialState(@NonNull PointF initialPosition, @NonNull PointF velocity,
|
||||
@Nullable Runnable endActions) {
|
||||
mBubbleAnimator.cancel();
|
||||
mBubbleAnimator
|
||||
.spring(DynamicAnimation.SCALE_X, 1f)
|
||||
.spring(DynamicAnimation.SCALE_Y, 1f)
|
||||
.spring(DynamicAnimation.TRANSLATION_X, initialPosition.x, velocity.x,
|
||||
mTranslationConfig)
|
||||
.spring(DynamicAnimation.TRANSLATION_Y, initialPosition.y, velocity.y,
|
||||
mTranslationConfig)
|
||||
.addEndListener((View target, @NonNull FloatPropertyCompat<? super View> property,
|
||||
boolean wasFling, boolean canceled, float finalValue, float finalVelocity,
|
||||
boolean allRelevantPropertyAnimationsEnded) -> {
|
||||
if (canceled || allRelevantPropertyAnimationsEnded) {
|
||||
resetAnimatedViews(initialPosition);
|
||||
if (endActions != null) {
|
||||
endActions.run();
|
||||
}
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the dragged view alongside the dismiss view when it gets captured in the dismiss
|
||||
* target area.
|
||||
*/
|
||||
public void animateDismissCaptured() {
|
||||
mBubbleAnimator.cancel();
|
||||
mBubbleAnimator
|
||||
.spring(DynamicAnimation.SCALE_X, mBubbleCapturedScale)
|
||||
.spring(DynamicAnimation.SCALE_Y, mBubbleCapturedScale)
|
||||
.spring(DynamicAnimation.ALPHA, mDismissCapturedScale)
|
||||
.start();
|
||||
|
||||
if (mDismissAnimator != null) {
|
||||
mDismissAnimator.cancel();
|
||||
mDismissAnimator
|
||||
.spring(DynamicAnimation.SCALE_X, mDismissCapturedScale)
|
||||
.spring(DynamicAnimation.SCALE_Y, mDismissCapturedScale)
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the dragged view alongside the dismiss view when it gets released from the dismiss
|
||||
* target area.
|
||||
*/
|
||||
public void animateDismissReleased() {
|
||||
mBubbleAnimator.cancel();
|
||||
mBubbleAnimator
|
||||
.spring(DynamicAnimation.SCALE_X, mBubbleFocusedScale)
|
||||
.spring(DynamicAnimation.SCALE_Y, mBubbleFocusedScale)
|
||||
.spring(DynamicAnimation.ALPHA, 1f)
|
||||
.start();
|
||||
|
||||
if (mDismissAnimator != null) {
|
||||
mDismissAnimator.cancel();
|
||||
mDismissAnimator
|
||||
.spring(DynamicAnimation.SCALE_X, 1f)
|
||||
.spring(DynamicAnimation.SCALE_Y, 1f)
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the dragged bubble dismiss when it's released in the dismiss target area.
|
||||
*
|
||||
* @param initialPosition the initial position to move the bubble too after animation finishes
|
||||
* @param endActions gets called when the animation completes or gets cancelled
|
||||
*/
|
||||
public void animateDismiss(@NonNull PointF initialPosition, @Nullable Runnable endActions) {
|
||||
float dismissHeight = mDismissView != null ? mDismissView.getHeight() : 0f;
|
||||
float translationY = mView.getTranslationY() + dismissHeight;
|
||||
mBubbleAnimator
|
||||
.spring(DynamicAnimation.TRANSLATION_Y, translationY)
|
||||
.spring(DynamicAnimation.SCALE_X, 0f)
|
||||
.spring(DynamicAnimation.SCALE_Y, 0f)
|
||||
.spring(DynamicAnimation.ALPHA, 0f)
|
||||
.addEndListener((View target, @NonNull FloatPropertyCompat<? super View> property,
|
||||
boolean wasFling, boolean canceled, float finalValue, float finalVelocity,
|
||||
boolean allRelevantPropertyAnimationsEnded) -> {
|
||||
if (canceled || allRelevantPropertyAnimationsEnded) {
|
||||
resetAnimatedViews(initialPosition);
|
||||
if (endActions != null) endActions.run();
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the animated views to the initial state
|
||||
*
|
||||
* @param initialPosition position of the bubble
|
||||
*/
|
||||
private void resetAnimatedViews(@NonNull PointF initialPosition) {
|
||||
mView.setScaleX(1f);
|
||||
mView.setScaleY(1f);
|
||||
mView.setAlpha(1f);
|
||||
mView.setTranslationX(initialPosition.x);
|
||||
mView.setTranslationY(initialPosition.y);
|
||||
|
||||
if (mDismissView != null) {
|
||||
mDismissView.getCircle().setScaleX(1f);
|
||||
mDismissView.getCircle().setScaleY(1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.bubbles;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.PointF;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
|
||||
/**
|
||||
* Controls bubble bar drag to dismiss interaction.
|
||||
* Interacts with {@link BubbleDismissController}, used by {@link BubbleBarViewController}.
|
||||
* Supported interactions:
|
||||
* - Drag a single bubble view into dismiss target to remove it.
|
||||
* - Drag the bubble stack into dismiss target to remove all.
|
||||
* Restores initial position of dragged view if released outside of the dismiss target.
|
||||
*/
|
||||
public class BubbleDragController {
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private BubbleBarViewController mBubbleBarViewController;
|
||||
private BubbleDismissController mBubbleDismissController;
|
||||
|
||||
public BubbleDragController(TaskbarActivityContext activity) {
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes dependencies when bubble controllers are created.
|
||||
* 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(@NonNull BubbleControllers bubbleControllers) {
|
||||
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
|
||||
mBubbleDismissController = bubbleControllers.bubbleDismissController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the bubble view for dragging and attach touch listener to it
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public void setupBubbleView(@NonNull BubbleView bubbleView) {
|
||||
if (!(bubbleView.getBubble() instanceof BubbleBarBubble)) {
|
||||
// Don't setup dragging for overflow bubble view
|
||||
return;
|
||||
}
|
||||
|
||||
bubbleView.setOnTouchListener(new BubbleTouchListener() {
|
||||
@Override
|
||||
void onDragStart() {
|
||||
mBubbleBarViewController.onDragStart(bubbleView);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDragEnd() {
|
||||
mBubbleBarViewController.onDragEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDragRelease() {
|
||||
mBubbleBarViewController.onDragRelease(bubbleView);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the bubble bar view for dragging and attach touch listener to it
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public void setupBubbleBarView(@NonNull BubbleBarView bubbleBarView) {
|
||||
PointF initialRelativePivot = new PointF();
|
||||
bubbleBarView.setOnTouchListener(new BubbleTouchListener() {
|
||||
@Override
|
||||
protected boolean onTouchDown(@NonNull View view, @NonNull MotionEvent event) {
|
||||
if (bubbleBarView.isExpanded()) return false;
|
||||
return super.onTouchDown(view, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDragStart() {
|
||||
initialRelativePivot.set(bubbleBarView.getRelativePivotX(),
|
||||
bubbleBarView.getRelativePivotY());
|
||||
// By default the bubble bar view pivot is in bottom right corner, while dragging
|
||||
// it should be centered in order to align it with the dismiss target view
|
||||
bubbleBarView.setRelativePivot(/* x = */ 0.5f, /* y = */ 0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDragEnd() {
|
||||
// Restoring the initial pivot for the bubble bar view
|
||||
bubbleBarView.setRelativePivot(initialRelativePivot.x, initialRelativePivot.y);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bubble touch listener for handling a single bubble view or bubble bar view while dragging.
|
||||
* The dragging starts after "shorter" long click (the long click duration might change):
|
||||
* - When the touch gesture moves out of the {@code ACTION_DOWN} location the dragging
|
||||
* interaction is cancelled.
|
||||
* - When {@code ACTION_UP} happens before long click is registered and there was no significant
|
||||
* movement the view will perform click.
|
||||
* - When the listener registers long click it starts dragging interaction, all the subsequent
|
||||
* {@code ACTION_MOVE} events will drag the view, and the interaction finishes when
|
||||
* {@code ACTION_UP} or {@code ACTION_CANCEL} are received.
|
||||
* Lifecycle methods can be overridden do add extra setup/clean up steps.
|
||||
*/
|
||||
private abstract class BubbleTouchListener implements View.OnTouchListener {
|
||||
/**
|
||||
* The internal state of the touch listener
|
||||
*/
|
||||
private enum State {
|
||||
// Idle and ready for the touch events.
|
||||
// Changes to:
|
||||
// - TOUCHED, when the {@code ACTION_DOWN} is handled
|
||||
IDLE,
|
||||
|
||||
// Touch down was handled and the lister is recognising the gestures.
|
||||
// Changes to:
|
||||
// - IDLE, when performs the click
|
||||
// - DRAGGING, when registers the long click and starts dragging interaction
|
||||
// - CANCELLED, when the touch events move out of the initial location before the long
|
||||
// click is recognised
|
||||
|
||||
TOUCHED,
|
||||
|
||||
// The long click was registered and the view is being dragged.
|
||||
// Changes to:
|
||||
// - IDLE, when the gesture ends with the {@code ACTION_UP} or {@code ACTION_CANCEL}
|
||||
DRAGGING,
|
||||
|
||||
// The dragging was cancelled.
|
||||
// Changes to:
|
||||
// - IDLE, when the current gesture completes
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
private final PointF mTouchDownLocation = new PointF();
|
||||
private final PointF mViewInitialPosition = new PointF();
|
||||
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
|
||||
private final long mPressToDragTimeout = ViewConfiguration.getLongPressTimeout() / 2;
|
||||
private State mState = State.IDLE;
|
||||
private int mTouchSlop = -1;
|
||||
private BubbleDragAnimator mAnimator;
|
||||
@Nullable
|
||||
private Runnable mLongClickRunnable;
|
||||
|
||||
/**
|
||||
* Called when the dragging interaction has started
|
||||
*/
|
||||
abstract void onDragStart();
|
||||
|
||||
/**
|
||||
* Called when the dragging interaction has ended and all the animations have completed
|
||||
*/
|
||||
abstract void onDragEnd();
|
||||
|
||||
/**
|
||||
* Called when the dragged bubble is released outside of the dismiss target area and will
|
||||
* move back to its initial position
|
||||
*/
|
||||
protected void onDragRelease() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the dragged bubble is released inside of the dismiss target area and will get
|
||||
* dismissed with animation
|
||||
*/
|
||||
protected void onDragDismiss() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public boolean onTouch(@NonNull View view, @NonNull MotionEvent event) {
|
||||
updateVelocity(event);
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
return onTouchDown(view, event);
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
onTouchMove(view, event);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
onTouchUp(view, event);
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
onTouchCancel(view, event);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The touch down starts the interaction and schedules the long click handler.
|
||||
*
|
||||
* @param view the view that received the event
|
||||
* @param event the motion event
|
||||
* @return true if the gesture should be intercepted and handled, false otherwise. Note if
|
||||
* the false is returned subsequent events in the gesture won't get reported.
|
||||
*/
|
||||
protected boolean onTouchDown(@NonNull View view, @NonNull MotionEvent event) {
|
||||
mState = State.TOUCHED;
|
||||
mTouchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop();
|
||||
mTouchDownLocation.set(event.getRawX(), event.getRawY());
|
||||
mViewInitialPosition.set(view.getTranslationX(), view.getTranslationY());
|
||||
setupLongClickHandler(view);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The move event drags the view or cancels the interaction if hasn't long clicked yet.
|
||||
*
|
||||
* @param view the view that received the event
|
||||
* @param event the motion event
|
||||
*/
|
||||
protected void onTouchMove(@NonNull View view, @NonNull MotionEvent event) {
|
||||
final float dx = event.getRawX() - mTouchDownLocation.x;
|
||||
final float dy = event.getRawY() - mTouchDownLocation.y;
|
||||
switch (mState) {
|
||||
case TOUCHED:
|
||||
final boolean movedOut = Math.hypot(dx, dy) > mTouchSlop;
|
||||
if (movedOut) {
|
||||
// Moved out of the initial location before the long click was registered
|
||||
mState = State.CANCELLED;
|
||||
cleanUpLongClickHandler(view);
|
||||
}
|
||||
break;
|
||||
case DRAGGING:
|
||||
drag(view, event, dx, dy);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On touch up performs click or finishes the dragging depending on the state.
|
||||
*
|
||||
* @param view the view that received the event
|
||||
* @param event the motion event
|
||||
*/
|
||||
protected void onTouchUp(@NonNull View view, @NonNull MotionEvent event) {
|
||||
switch (mState) {
|
||||
case TOUCHED:
|
||||
view.performClick();
|
||||
cleanUp(view);
|
||||
break;
|
||||
case DRAGGING:
|
||||
stopDragging(view, event);
|
||||
break;
|
||||
default:
|
||||
cleanUp(view);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The gesture is cancelled and the interaction should clean up and complete.
|
||||
*
|
||||
* @param view the view that received the event
|
||||
* @param event the motion event
|
||||
*/
|
||||
protected void onTouchCancel(@NonNull View view, @NonNull MotionEvent event) {
|
||||
if (mState == State.DRAGGING) {
|
||||
stopDragging(view, event);
|
||||
} else {
|
||||
cleanUp(view);
|
||||
}
|
||||
}
|
||||
|
||||
private void startDragging(@NonNull View view) {
|
||||
onDragStart();
|
||||
mActivity.setTaskbarWindowFullscreen(true);
|
||||
mAnimator = new BubbleDragAnimator(view);
|
||||
mAnimator.animateFocused();
|
||||
mBubbleDismissController.setupDismissView(view, mAnimator);
|
||||
mBubbleDismissController.showDismissView();
|
||||
}
|
||||
|
||||
private void drag(@NonNull View view, @NonNull MotionEvent event, float dx, float dy) {
|
||||
if (mBubbleDismissController.handleTouchEvent(event)) return;
|
||||
view.setTranslationX(mViewInitialPosition.x + dx);
|
||||
view.setTranslationY(mViewInitialPosition.y + dy);
|
||||
}
|
||||
|
||||
private void stopDragging(@NonNull View view, @NonNull MotionEvent event) {
|
||||
Runnable onComplete = () -> {
|
||||
mActivity.setTaskbarWindowFullscreen(false);
|
||||
cleanUp(view);
|
||||
onDragEnd();
|
||||
};
|
||||
|
||||
if (mBubbleDismissController.handleTouchEvent(event)) {
|
||||
onDragDismiss();
|
||||
mAnimator.animateDismiss(mViewInitialPosition, onComplete);
|
||||
} else {
|
||||
onDragRelease();
|
||||
mAnimator.animateToInitialState(mViewInitialPosition, getCurrentVelocity(),
|
||||
onComplete);
|
||||
}
|
||||
mBubbleDismissController.hideDismissView();
|
||||
}
|
||||
|
||||
private void setupLongClickHandler(@NonNull View view) {
|
||||
cleanUpLongClickHandler(view);
|
||||
mLongClickRunnable = () -> {
|
||||
// Register long click and start dragging interaction
|
||||
mState = State.DRAGGING;
|
||||
startDragging(view);
|
||||
};
|
||||
view.getHandler().postDelayed(mLongClickRunnable, mPressToDragTimeout);
|
||||
}
|
||||
|
||||
private void cleanUpLongClickHandler(@NonNull View view) {
|
||||
if (mLongClickRunnable == null || view.getHandler() == null) return;
|
||||
view.getHandler().removeCallbacks(mLongClickRunnable);
|
||||
mLongClickRunnable = null;
|
||||
}
|
||||
|
||||
private void cleanUp(@NonNull View view) {
|
||||
cleanUpLongClickHandler(view);
|
||||
mVelocityTracker.clear();
|
||||
mState = State.IDLE;
|
||||
}
|
||||
|
||||
private void updateVelocity(MotionEvent event) {
|
||||
final float deltaX = event.getRawX() - event.getX();
|
||||
final float deltaY = event.getRawY() - event.getY();
|
||||
event.offsetLocation(deltaX, deltaY);
|
||||
mVelocityTracker.addMovement(event);
|
||||
event.offsetLocation(-deltaX, -deltaY);
|
||||
}
|
||||
|
||||
private PointF getCurrentVelocity() {
|
||||
mVelocityTracker.computeCurrentVelocity(/* units = */ 1000);
|
||||
return new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar.bubbles;
|
||||
|
||||
import static java.lang.Math.abs;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
@@ -25,11 +27,13 @@ import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.taskbar.StashedHandleViewController;
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
import com.android.launcher3.taskbar.TaskbarControllers;
|
||||
import com.android.launcher3.taskbar.TaskbarInsetsController;
|
||||
import com.android.launcher3.taskbar.TaskbarStashController;
|
||||
import com.android.launcher3.util.MultiPropertyFactory;
|
||||
|
||||
/**
|
||||
* Coordinates between controllers such as BubbleBarView and BubbleHandleViewController to
|
||||
* Coordinates between controllers such as BubbleBarView and
|
||||
* BubbleHandleViewController to
|
||||
* create a cohesive animation between stashed/unstashed states.
|
||||
*/
|
||||
public class BubbleStashController {
|
||||
@@ -50,6 +54,7 @@ public class BubbleStashController {
|
||||
|
||||
// Initialized in init.
|
||||
private TaskbarControllers mControllers;
|
||||
private TaskbarInsetsController mTaskbarInsetsController;
|
||||
private BubbleBarViewController mBarViewController;
|
||||
private BubbleStashedHandleViewController mHandleViewController;
|
||||
private TaskbarStashController mTaskbarStashController;
|
||||
@@ -67,6 +72,7 @@ public class BubbleStashController {
|
||||
private int mUnstashedHeight;
|
||||
private boolean mBubblesShowingOnHome;
|
||||
private boolean mBubblesShowingOnOverview;
|
||||
private boolean mIsSysuiLocked;
|
||||
|
||||
@Nullable
|
||||
private AnimatorSet mAnimator;
|
||||
@@ -77,6 +83,7 @@ public class BubbleStashController {
|
||||
|
||||
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
|
||||
mControllers = controllers;
|
||||
mTaskbarInsetsController = controllers.taskbarInsetsController;
|
||||
mBarViewController = bubbleControllers.bubbleBarViewController;
|
||||
mHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
|
||||
mTaskbarStashController = controllers.taskbarStashController;
|
||||
@@ -90,14 +97,6 @@ public class BubbleStashController {
|
||||
|
||||
mStashedHeight = mHandleViewController.getStashedHeight();
|
||||
mUnstashedHeight = mHandleViewController.getUnstashedHeight();
|
||||
|
||||
bubbleControllers.runAfterInit(() -> {
|
||||
if (mTaskbarStashController.isStashed()) {
|
||||
stashBubbleBar();
|
||||
} else {
|
||||
showBubbleBar(false /* expandBubbles */);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,13 +114,71 @@ public class BubbleStashController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when launcher enters or exits the home page. Bubbles are unstashed on home.
|
||||
* Animates the bubble bar and handle to their initial state, transitioning from
|
||||
* the state where
|
||||
* both views are invisible. Called when the first bubble is added or when the
|
||||
* device is
|
||||
* unlocked.
|
||||
*
|
||||
* <p>
|
||||
* Normally either the bubble bar or the handle is visible,
|
||||
* and {@link #showBubbleBar(boolean)} and {@link #stashBubbleBar()} are used to
|
||||
* transition
|
||||
* between these two states. But the transition from the state where both the
|
||||
* bar and handle
|
||||
* are invisible is slightly different.
|
||||
*
|
||||
* <p>
|
||||
* The initial state will depend on the current state of the device, i.e.
|
||||
* overview, home etc
|
||||
* and whether bubbles are requested to be expanded.
|
||||
*/
|
||||
public void animateToInitialState(boolean expanding) {
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
if (expanding || mBubblesShowingOnHome || mBubblesShowingOnOverview) {
|
||||
mIsStashed = false;
|
||||
animatorSet.playTogether(mIconScaleForStash.animateToValue(1),
|
||||
mIconTranslationYForStash.animateToValue(getBubbleBarTranslationY()),
|
||||
mIconAlphaForStash.animateToValue(1));
|
||||
} else {
|
||||
mIsStashed = true;
|
||||
animatorSet.playTogether(mBubbleStashedHandleAlpha.animateToValue(1));
|
||||
}
|
||||
|
||||
animatorSet.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
onIsStashedChanged();
|
||||
}
|
||||
});
|
||||
animatorSet.setDuration(BAR_STASH_DURATION).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when launcher enters or exits the home page. Bubbles are unstashed on
|
||||
* home.
|
||||
*/
|
||||
public void setBubblesShowingOnHome(boolean onHome) {
|
||||
if (mBubblesShowingOnHome != onHome) {
|
||||
mBubblesShowingOnHome = onHome;
|
||||
|
||||
if (!mBarViewController.hasBubbles()) {
|
||||
// if there are no bubbles, there's nothing to show, so just return.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mBubblesShowingOnHome) {
|
||||
showBubbleBar(/* expanded= */ false);
|
||||
// When transitioning from app to home the stash animator may already have been
|
||||
// created, so we need to animate the bubble bar here to align with hotseat.
|
||||
if (!mIsStashed) {
|
||||
mIconTranslationYForStash.animateToValue(getBubbleBarTranslationYForHotseat())
|
||||
.start();
|
||||
}
|
||||
// If the bubble bar is already unstashed, the taskbar touchable region won't be
|
||||
// updated correctly, so force an update here.
|
||||
mControllers
|
||||
.runAfterInit(() -> mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged());
|
||||
} else if (!mBarViewController.isExpanded()) {
|
||||
stashBubbleBar();
|
||||
}
|
||||
@@ -133,30 +190,46 @@ public class BubbleStashController {
|
||||
return mBubblesShowingOnHome;
|
||||
}
|
||||
|
||||
// TODO: when tapping on an app in overview, this is a bit delayed compared to taskbar stashing
|
||||
/** Called when launcher enters or exits overview. Bubbles are unstashed on overview. */
|
||||
// TODO: when tapping on an app in overview, this is a bit delayed compared to
|
||||
// taskbar stashing
|
||||
/**
|
||||
* Called when launcher enters or exits overview. Bubbles are unstashed on
|
||||
* overview.
|
||||
*/
|
||||
public void setBubblesShowingOnOverview(boolean onOverview) {
|
||||
if (mBubblesShowingOnOverview != onOverview) {
|
||||
mBubblesShowingOnOverview = onOverview;
|
||||
if (!mBubblesShowingOnOverview && !mBarViewController.isExpanded()) {
|
||||
stashBubbleBar();
|
||||
} else {
|
||||
// When transitioning to overview the stash animator may already have been
|
||||
// created, so we need to animate the bubble bar here to align with taskbar.
|
||||
mIconTranslationYForStash.animateToValue(getBubbleBarTranslationYForTaskbar())
|
||||
.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when sysui locked state changes, when locked, bubble bar is stashed. */
|
||||
/** Whether bubbles are showing on Overview. */
|
||||
public boolean isBubblesShowingOnOverview() {
|
||||
return mBubblesShowingOnOverview;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when sysui locked state changes, when locked, bubble bar is stashed.
|
||||
*/
|
||||
public void onSysuiLockedStateChange(boolean isSysuiLocked) {
|
||||
if (isSysuiLocked) {
|
||||
// TODO: should the normal path flip mBubblesOnHome / check if this is needed
|
||||
// If we're locked, we're no longer showing on home.
|
||||
mBubblesShowingOnHome = false;
|
||||
mBubblesShowingOnOverview = false;
|
||||
stashBubbleBar();
|
||||
if (isSysuiLocked != mIsSysuiLocked) {
|
||||
mIsSysuiLocked = isSysuiLocked;
|
||||
if (!mIsSysuiLocked && mBarViewController.hasBubbles()) {
|
||||
animateToInitialState(false /* expanding */);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stashes the bubble bar if allowed based on other state (e.g. on home and overview the
|
||||
* Stashes the bubble bar if allowed based on other state (e.g. on home and
|
||||
* overview the
|
||||
* bar does not stash).
|
||||
*/
|
||||
public void stashBubbleBar() {
|
||||
@@ -166,7 +239,8 @@ public class BubbleStashController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the bubble bar, and expands bubbles depending on {@param expandBubbles}.
|
||||
* Shows the bubble bar, and expands bubbles depending on
|
||||
* {@param expandBubbles}.
|
||||
*/
|
||||
public void showBubbleBar(boolean expandBubbles) {
|
||||
mRequestedStashState = false;
|
||||
@@ -200,7 +274,7 @@ public class BubbleStashController {
|
||||
* Create a stash animation.
|
||||
*
|
||||
* @param isStashed whether it's a stash animation or an unstash animation
|
||||
* @param duration duration of the animation
|
||||
* @param duration duration of the animation
|
||||
* @return the animation
|
||||
*/
|
||||
private AnimatorSet createStashAnimator(boolean isStashed, long duration) {
|
||||
@@ -226,23 +300,20 @@ public class BubbleStashController {
|
||||
mIconScaleForStash.animateToValue(STASHED_BAR_SCALE));
|
||||
secondHalfAnimatorSet.playTogether(
|
||||
mBubbleStashedHandleAlpha.animateToValue(1));
|
||||
} else {
|
||||
} else {
|
||||
firstHalfDurationScale = 0.5f;
|
||||
secondHalfDurationScale = 0.75f;
|
||||
|
||||
// If we're on home, adjust the translation so the bubble bar aligns with hotseat.
|
||||
final float hotseatTransY = mActivity.getDeviceProfile().getTaskbarOffsetY();
|
||||
final float translationY = mBubblesShowingOnHome ? hotseatTransY : 0;
|
||||
final float translationY = getBubbleBarTranslationY();
|
||||
|
||||
fullLengthAnimatorSet.playTogether(
|
||||
mIconScaleForStash.animateToValue(1),
|
||||
mIconTranslationYForStash.animateToValue(translationY));
|
||||
|
||||
firstHalfAnimatorSet.playTogether(
|
||||
mBubbleStashedHandleAlpha.animateToValue(0)
|
||||
);
|
||||
mBubbleStashedHandleAlpha.animateToValue(0));
|
||||
secondHalfAnimatorSet.playTogether(
|
||||
mIconAlphaForStash.animateToValue(1)
|
||||
);
|
||||
mIconAlphaForStash.animateToValue(1));
|
||||
}
|
||||
|
||||
fullLengthAnimatorSet.play(mHandleViewController.createRevealAnimToIsStashed(isStashed));
|
||||
@@ -262,6 +333,7 @@ public class BubbleStashController {
|
||||
if (isStashed) {
|
||||
mBarViewController.setExpanded(false);
|
||||
}
|
||||
mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -271,7 +343,28 @@ public class BubbleStashController {
|
||||
private void onIsStashedChanged() {
|
||||
mControllers.runAfterInit(() -> {
|
||||
mHandleViewController.onIsStashedChanged();
|
||||
// TODO: when stash changes tell taskbarInsetsController the insets have changed.
|
||||
mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
|
||||
});
|
||||
}
|
||||
|
||||
private float getBubbleBarTranslationYForTaskbar() {
|
||||
return -mActivity.getDeviceProfile().taskbarBottomMargin;
|
||||
}
|
||||
|
||||
private float getBubbleBarTranslationYForHotseat() {
|
||||
final float hotseatBottomSpace = mActivity.getDeviceProfile().hotseatBarBottomSpacePx;
|
||||
final float hotseatCellHeight = mActivity.getDeviceProfile().hotseatCellHeightPx;
|
||||
return -hotseatBottomSpace - hotseatCellHeight + mUnstashedHeight - abs(
|
||||
hotseatCellHeight - mUnstashedHeight) / 2;
|
||||
}
|
||||
|
||||
float getBubbleBarTranslationY() {
|
||||
// If we're on home, adjust the translation so the bubble bar aligns with
|
||||
// hotseat.
|
||||
// Otherwise we're either showing in an app or in overview. In either case
|
||||
// adjust it so
|
||||
// the bubble bar aligns with the taskbar.
|
||||
return mBubblesShowingOnHome ? getBubbleBarTranslationYForHotseat()
|
||||
: getBubbleBarTranslationYForTaskbar();
|
||||
}
|
||||
}
|
||||
|
||||
+51
-32
@@ -39,7 +39,8 @@ import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
|
||||
|
||||
/**
|
||||
* Handles properties/data collection, then passes the results to our stashed handle View to render.
|
||||
* Handles properties/data collection, then passes the results to our stashed
|
||||
* handle View to render.
|
||||
*/
|
||||
public class BubbleStashedHandleViewController {
|
||||
|
||||
@@ -52,13 +53,16 @@ public class BubbleStashedHandleViewController {
|
||||
private BubbleStashController mBubbleStashController;
|
||||
private RegionSamplingHelper mRegionSamplingHelper;
|
||||
private int mBarSize;
|
||||
private int mStashedTaskbarHeight;
|
||||
private int mStashedHandleWidth;
|
||||
private int mStashedHandleHeight;
|
||||
|
||||
// The bounds we want to clip to in the settled state when showing the stashed handle.
|
||||
// The bounds we want to clip to in the settled state when showing the stashed
|
||||
// handle.
|
||||
private final Rect mStashedHandleBounds = new Rect();
|
||||
|
||||
// When the reveal animation is cancelled, we can assume it's about to create a new animation,
|
||||
// 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;
|
||||
@@ -92,7 +96,7 @@ public class BubbleStashedHandleViewController {
|
||||
|
||||
mTaskbarStashedHandleAlpha.get(0).setValue(0);
|
||||
|
||||
final int stashedTaskbarHeight = resources.getDimensionPixelSize(
|
||||
mStashedTaskbarHeight = resources.getDimensionPixelSize(
|
||||
R.dimen.bubblebar_stashed_size);
|
||||
mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() {
|
||||
@Override
|
||||
@@ -115,22 +119,26 @@ public class BubbleStashedHandleViewController {
|
||||
}
|
||||
}, Executors.UI_HELPER_EXECUTOR);
|
||||
|
||||
mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> {
|
||||
// As more bubbles get added, the icon bounds become larger. To ensure a consistent
|
||||
// handle bar position, we pin it to the edge of the screen.
|
||||
Rect bubblebarRect = mBarViewController.getBubbleBarBounds();
|
||||
final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2;
|
||||
mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> updateBounds());
|
||||
}
|
||||
|
||||
mStashedHandleBounds.set(
|
||||
bubblebarRect.right - mStashedHandleWidth,
|
||||
stashedCenterY - mStashedHandleHeight / 2,
|
||||
bubblebarRect.right,
|
||||
stashedCenterY + mStashedHandleHeight / 2);
|
||||
mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
|
||||
private void updateBounds() {
|
||||
// As more bubbles get added, the icon bounds become larger. To ensure a
|
||||
// consistent
|
||||
// handle bar position, we pin it to the edge of the screen.
|
||||
final int right = mActivity.getDeviceProfile().widthPx - mBarViewController.getHorizontalMargin();
|
||||
|
||||
view.setPivotX(view.getWidth());
|
||||
view.setPivotY(view.getHeight() - stashedTaskbarHeight / 2f);
|
||||
});
|
||||
final int stashedCenterY = mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2;
|
||||
|
||||
mStashedHandleBounds.set(
|
||||
right - mStashedHandleWidth,
|
||||
stashedCenterY - mStashedHandleHeight / 2,
|
||||
right,
|
||||
stashedCenterY + mStashedHandleHeight / 2);
|
||||
mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
|
||||
|
||||
mStashedHandleView.setPivotX(mStashedHandleView.getWidth());
|
||||
mStashedHandleView.setPivotY(mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2f);
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
@@ -146,14 +154,16 @@ public class BubbleStashedHandleViewController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height when the bubble bar is unstashed (so the height of the bubble bar).
|
||||
* Returns the height when the bubble bar is unstashed (so the height of the
|
||||
* bubble bar).
|
||||
*/
|
||||
public int getUnstashedHeight() {
|
||||
return mBarSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when system ui state changes. Bubbles don't show when the device is locked.
|
||||
* Called when system ui state changes. Bubbles don't show when the device is
|
||||
* locked.
|
||||
*/
|
||||
public void setHiddenForSysui(boolean hidden) {
|
||||
if (mHiddenForSysui != hidden) {
|
||||
@@ -163,7 +173,8 @@ public class BubbleStashedHandleViewController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the handle should be hidden (or shown) because there are no bubbles
|
||||
* Called when the handle should be hidden (or shown) because there are no
|
||||
* bubbles
|
||||
* (or 1+ bubbles).
|
||||
*/
|
||||
public void setHiddenForBubbles(boolean hidden) {
|
||||
@@ -174,7 +185,8 @@ public class BubbleStashedHandleViewController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the home button is enabled / disabled. Bubbles don't show if home is disabled.
|
||||
* Called when the home button is enabled / disabled. Bubbles don't show if home
|
||||
* is disabled.
|
||||
*/
|
||||
// TODO: is this needed for bubbles?
|
||||
public void setIsHomeButtonDisabled(boolean homeDisabled) {
|
||||
@@ -188,12 +200,14 @@ public class BubbleStashedHandleViewController {
|
||||
mStashedHandleView.setVisibility(VISIBLE);
|
||||
} else {
|
||||
mStashedHandleView.setVisibility(INVISIBLE);
|
||||
mStashedHandleView.setAlpha(0);
|
||||
}
|
||||
updateRegionSampling();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when bubble bar is stash state changes so that updates to the stashed handle color
|
||||
* Called when bubble bar is stash state changes so that updates to the stashed
|
||||
* handle color
|
||||
* can be started or stopped.
|
||||
*/
|
||||
public void onIsStashedChanged() {
|
||||
@@ -203,12 +217,14 @@ public class BubbleStashedHandleViewController {
|
||||
private void updateRegionSampling() {
|
||||
boolean handleVisible = mStashedHandleView.getVisibility() == VISIBLE
|
||||
&& mBubbleStashController.isStashed();
|
||||
mRegionSamplingHelper.setWindowVisible(handleVisible);
|
||||
if (handleVisible) {
|
||||
mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
|
||||
mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion());
|
||||
} else {
|
||||
mRegionSamplingHelper.stop();
|
||||
if (mRegionSamplingHelper != null) {
|
||||
mRegionSamplingHelper.setWindowVisible(handleVisible);
|
||||
if (handleVisible) {
|
||||
mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
|
||||
mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion());
|
||||
} else {
|
||||
mRegionSamplingHelper.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,15 +236,18 @@ public class BubbleStashedHandleViewController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by {@link BubbleStashController} to animate the handle when stashing or un stashing.
|
||||
* Used by {@link BubbleStashController} to animate the handle when stashing or
|
||||
* un stashing.
|
||||
*/
|
||||
public MultiPropertyFactory<View> getStashedHandleAlpha() {
|
||||
return mTaskbarStashedHandleAlpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns an Animator that updates the stashed handle shape and size.
|
||||
* When stashed, the shape is a thin rounded pill. When unstashed, the shape morphs into
|
||||
* Creates and returns an 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 bubble bar icons will be.
|
||||
*/
|
||||
public Animator createRevealAnimToIsStashed(boolean isStashed) {
|
||||
|
||||
@@ -18,7 +18,9 @@ package com.android.launcher3.taskbar.bubbles;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -28,27 +30,66 @@ import android.widget.ImageView;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.icons.DotRenderer;
|
||||
import com.android.launcher3.icons.IconNormalizer;
|
||||
import com.android.wm.shell.animation.Interpolators;
|
||||
import com.patrykmichalik.opto.core.PreferenceExtensionsKt;
|
||||
import app.lawnchair.preferences2.PreferenceManager2;
|
||||
import app.lawnchair.theme.color.ColorOption;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
// TODO: (b/276978250) This is will be similar to WMShell's BadgedImageView, it'd be nice to share.
|
||||
// TODO: (b/269670235) currently this doesn't show the 'update dot'
|
||||
|
||||
/**
|
||||
* View that displays a bubble icon, along with an app badge on either the left or
|
||||
* View that displays a bubble icon, along with an app badge on either the left
|
||||
* or
|
||||
* right side of the view.
|
||||
*/
|
||||
public class BubbleView extends ConstraintLayout {
|
||||
|
||||
// TODO: (b/269670235) currently we don't render the 'update dot', this will be used for that.
|
||||
public static final int DEFAULT_PATH_SIZE = 100;
|
||||
|
||||
/**
|
||||
* Flags that suppress the visibility of the 'new' dot or the app badge, for one
|
||||
* reason or
|
||||
* another. If any of these flags are set, the dot will not be shown.
|
||||
* If {@link SuppressionFlag#BEHIND_STACK} then the app badge will not be shown.
|
||||
*/
|
||||
enum SuppressionFlag {
|
||||
// TODO: (b/277815200) implement flyout
|
||||
// Suppressed because the flyout is visible - it will morph into the dot via
|
||||
// animation.
|
||||
FLYOUT_VISIBLE,
|
||||
// Suppressed because this bubble is behind others in the collapsed stack.
|
||||
BEHIND_STACK,
|
||||
}
|
||||
|
||||
private final EnumSet<SuppressionFlag> mSuppressionFlags = EnumSet.noneOf(SuppressionFlag.class);
|
||||
|
||||
private final ImageView mBubbleIcon;
|
||||
private final ImageView mAppIcon;
|
||||
private final int mBubbleSize;
|
||||
|
||||
private DotRenderer mDotRenderer;
|
||||
private DotRenderer.DrawParams mDrawParams;
|
||||
private int mDotColor;
|
||||
private Rect mTempBounds = new Rect();
|
||||
|
||||
// Whether the dot is animating
|
||||
private boolean mDotIsAnimating;
|
||||
// What scale value the dot is animating to
|
||||
private float mAnimatingToDotScale;
|
||||
// The current scale value of the dot
|
||||
private float mDotScale;
|
||||
|
||||
// TODO: (b/273310265) handle RTL
|
||||
// Whether the bubbles are positioned on the left or right side of the screen
|
||||
private boolean mOnLeft = false;
|
||||
|
||||
private BubbleBarBubble mBubble;
|
||||
private BubbleBarItem mBubble;
|
||||
|
||||
PreferenceManager2 preferenceManager2;
|
||||
|
||||
public BubbleView(Context context) {
|
||||
this(context, null);
|
||||
@@ -74,6 +115,10 @@ public class BubbleView extends ConstraintLayout {
|
||||
mBubbleIcon = findViewById(R.id.icon_view);
|
||||
mAppIcon = findViewById(R.id.app_icon_view);
|
||||
|
||||
preferenceManager2 = PreferenceManager2.INSTANCE.get(context);
|
||||
|
||||
mDrawParams = new DotRenderer.DrawParams();
|
||||
|
||||
setFocusable(true);
|
||||
setClickable(true);
|
||||
setOutlineProvider(new ViewOutlineProvider() {
|
||||
@@ -90,45 +135,155 @@ public class BubbleView extends ConstraintLayout {
|
||||
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchDraw(Canvas canvas) {
|
||||
super.dispatchDraw(canvas);
|
||||
|
||||
if (!shouldDrawDot()) {
|
||||
return;
|
||||
}
|
||||
|
||||
getDrawingRect(mTempBounds);
|
||||
|
||||
mDrawParams.appColor = mDotColor;
|
||||
mDrawParams.iconBounds = mTempBounds;
|
||||
mDrawParams.leftAlign = mOnLeft;
|
||||
mDrawParams.scale = mDotScale;
|
||||
|
||||
mDotRenderer.draw(canvas, mDrawParams, -1);
|
||||
}
|
||||
|
||||
/** Sets the bubble being rendered in this view. */
|
||||
void setBubble(BubbleBarBubble bubble) {
|
||||
mBubble = bubble;
|
||||
mBubbleIcon.setImageBitmap(bubble.getIcon());
|
||||
mAppIcon.setImageBitmap(bubble.getBadge());
|
||||
mDotColor = bubble.getDotColor();
|
||||
ColorOption dotColorOption = PreferenceExtensionsKt.firstBlocking(preferenceManager2.getNotificationDotColor());
|
||||
int dotColor = dotColorOption.getColorPreferenceEntry().getLightColor().invoke(getContext());
|
||||
ColorOption counterColorOption = PreferenceExtensionsKt
|
||||
.firstBlocking(preferenceManager2.getNotificationDotTextColor());
|
||||
int countColor = counterColorOption.getColorPreferenceEntry().getLightColor().invoke(getContext());
|
||||
mDotRenderer = new DotRenderer(mBubbleSize, bubble.getDotPath(), DEFAULT_PATH_SIZE, false, null, dotColor, countColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets that this bubble represents the overflow. The overflow appears in the
|
||||
* list of bubbles
|
||||
* but does not represent app content, instead it shows recent bubbles that
|
||||
* couldn't fit into
|
||||
* the list of bubbles. It doesn't show an app icon because it is part of system
|
||||
* UI / doesn't
|
||||
* come from an app.
|
||||
*/
|
||||
void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
|
||||
mBubble = overflow;
|
||||
mBubbleIcon.setImageBitmap(bitmap);
|
||||
mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
|
||||
}
|
||||
|
||||
/** Returns the bubble being rendered in this view. */
|
||||
@Nullable
|
||||
BubbleBarBubble getBubble() {
|
||||
BubbleBarItem getBubble() {
|
||||
return mBubble;
|
||||
}
|
||||
|
||||
/** Shows the app badge on this bubble. */
|
||||
void showBadge() {
|
||||
Bitmap appBadgeBitmap = mBubble.getBadge();
|
||||
if (appBadgeBitmap == null) {
|
||||
mAppIcon.setVisibility(GONE);
|
||||
void updateDotVisibility(boolean animate) {
|
||||
final float targetScale = shouldDrawDot() ? 1f : 0f;
|
||||
if (animate) {
|
||||
animateDotScale();
|
||||
} else {
|
||||
mDotScale = targetScale;
|
||||
mAnimatingToDotScale = targetScale;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
void updateBadgeVisibility() {
|
||||
if (mBubble instanceof BubbleBarOverflow) {
|
||||
// The overflow bubble does not have a badge, so just bail.
|
||||
return;
|
||||
}
|
||||
BubbleBarBubble bubble = (BubbleBarBubble) mBubble;
|
||||
Bitmap appBadgeBitmap = bubble.getBadge();
|
||||
int translationX = mOnLeft
|
||||
? -(bubble.getIcon().getWidth() - appBadgeBitmap.getWidth())
|
||||
: 0;
|
||||
mAppIcon.setTranslationX(translationX);
|
||||
mAppIcon.setVisibility(isBehindStack() ? GONE : VISIBLE);
|
||||
}
|
||||
|
||||
/** Sets whether this bubble is in the stack & not the first bubble. **/
|
||||
void setBehindStack(boolean behindStack, boolean animate) {
|
||||
if (behindStack) {
|
||||
mSuppressionFlags.add(SuppressionFlag.BEHIND_STACK);
|
||||
} else {
|
||||
mSuppressionFlags.remove(SuppressionFlag.BEHIND_STACK);
|
||||
}
|
||||
updateDotVisibility(animate);
|
||||
updateBadgeVisibility();
|
||||
}
|
||||
|
||||
/** Whether this bubble is in the stack & not the first bubble. **/
|
||||
boolean isBehindStack() {
|
||||
return mSuppressionFlags.contains(SuppressionFlag.BEHIND_STACK);
|
||||
}
|
||||
|
||||
/** Whether the dot indicating unseen content in a bubble should be shown. */
|
||||
private boolean shouldDrawDot() {
|
||||
boolean bubbleHasUnseenContent = mBubble != null
|
||||
&& mBubble instanceof BubbleBarBubble
|
||||
&& mSuppressionFlags.isEmpty()
|
||||
&& !((BubbleBarBubble) mBubble).getInfo().isNotificationSuppressed();
|
||||
|
||||
// Always render the dot if it's animating, since it could be animating out.
|
||||
// Otherwise, show
|
||||
// it if the bubble wants to show it, and we aren't suppressing it.
|
||||
return bubbleHasUnseenContent || mDotIsAnimating;
|
||||
}
|
||||
|
||||
/** How big the dot should be, fraction from 0 to 1. */
|
||||
private void setDotScale(float fraction) {
|
||||
mDotScale = fraction;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the dot to the given scale.
|
||||
*/
|
||||
private void animateDotScale() {
|
||||
float toScale = shouldDrawDot() ? 1f : 0f;
|
||||
mDotIsAnimating = true;
|
||||
|
||||
// Don't restart the animation if we're already animating to the given value.
|
||||
if (mAnimatingToDotScale == toScale || !shouldDrawDot()) {
|
||||
mDotIsAnimating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int translationX;
|
||||
if (mOnLeft) {
|
||||
translationX = -(mBubble.getIcon().getWidth() - appBadgeBitmap.getWidth());
|
||||
} else {
|
||||
translationX = 0;
|
||||
}
|
||||
mAnimatingToDotScale = toScale;
|
||||
|
||||
mAppIcon.setTranslationX(translationX);
|
||||
mAppIcon.setVisibility(VISIBLE);
|
||||
}
|
||||
final boolean showDot = toScale > 0f;
|
||||
|
||||
/** Hides the app badge on this bubble. */
|
||||
void hideBadge() {
|
||||
mAppIcon.setVisibility(GONE);
|
||||
// Do NOT wait until after animation ends to setShowDot
|
||||
// to avoid overriding more recent showDot states.
|
||||
clearAnimation();
|
||||
animate()
|
||||
.setDuration(200)
|
||||
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
|
||||
.setUpdateListener((valueAnimator) -> {
|
||||
float fraction = valueAnimator.getAnimatedFraction();
|
||||
fraction = showDot ? fraction : 1f - fraction;
|
||||
setDotScale(fraction);
|
||||
}).withEndAction(() -> {
|
||||
setDotScale(showDot ? 1f : 0f);
|
||||
mDotIsAnimating = false;
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BubbleView{" + mBubble + "}";
|
||||
String toString = mBubble != null ? mBubble.getKey() : "null";
|
||||
return "BubbleView{" + toString + "}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
madym@google.com
|
||||
+16
-3
@@ -17,10 +17,12 @@
|
||||
package com.android.launcher3.taskbar.navbutton
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.drawable.RotateDrawable
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.Utilities
|
||||
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
|
||||
|
||||
/**
|
||||
@@ -40,7 +42,18 @@ abstract class AbstractNavButtonLayoutter(
|
||||
protected val endContextualContainer: ViewGroup,
|
||||
protected val startContextualContainer: ViewGroup
|
||||
) : NavButtonLayoutter {
|
||||
protected val homeButton: ImageView = navButtonContainer.requireViewById(R.id.home)
|
||||
protected val recentsButton: ImageView = navButtonContainer.requireViewById(R.id.recent_apps)
|
||||
protected val backButton: ImageView = navButtonContainer.requireViewById(R.id.back)
|
||||
protected val homeButton: ImageView? = navButtonContainer.findViewById(R.id.home)
|
||||
protected val recentsButton: ImageView? = navButtonContainer.findViewById(R.id.recent_apps)
|
||||
protected val backButton: ImageView? = navButtonContainer.findViewById(R.id.back)
|
||||
|
||||
init {
|
||||
// setup back button drawable
|
||||
if (backButton != null) {
|
||||
val rotateDrawable = RotateDrawable()
|
||||
rotateDrawable.drawable = backButton.context?.getDrawable(R.drawable.ic_sysbar_back)
|
||||
rotateDrawable.fromDegrees = 0f
|
||||
rotateDrawable.toDegrees = if (Utilities.isRtl(backButton.resources)) 90f else -90f
|
||||
backButton.setImageDrawable(rotateDrawable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,10 +51,10 @@ class KidsNavLayoutter(
|
||||
val paddingTop = (buttonHeight - iconSize) / 2
|
||||
|
||||
// Update icons
|
||||
backButton.setImageDrawable(backButton.context.getDrawable(DRAWABLE_SYSBAR_BACK_KIDS))
|
||||
backButton!!.setImageDrawable(backButton.context.getDrawable(DRAWABLE_SYSBAR_BACK_KIDS))
|
||||
backButton.scaleType = ImageView.ScaleType.FIT_CENTER
|
||||
backButton.setPadding(paddingLeft, paddingTop, paddingLeft, paddingTop)
|
||||
homeButton.setImageDrawable(homeButton.getContext().getDrawable(DRAWABLE_SYSBAR_HOME_KIDS))
|
||||
homeButton!!.setImageDrawable(homeButton.context.getDrawable(DRAWABLE_SYSBAR_HOME_KIDS))
|
||||
homeButton.scaleType = ImageView.ScaleType.FIT_CENTER
|
||||
homeButton.setPadding(paddingLeft, paddingTop, paddingLeft, paddingTop)
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package com.android.launcher3.taskbar.navbutton
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.view.Surface.ROTATION_90
|
||||
import android.view.Surface.Rotation
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
@@ -56,7 +58,8 @@ class NavButtonLayoutFactory {
|
||||
isKidsMode: Boolean,
|
||||
isInSetup: Boolean,
|
||||
isThreeButtonNav: Boolean,
|
||||
phoneMode: Boolean
|
||||
phoneMode: Boolean,
|
||||
@Rotation surfaceRotation: Int
|
||||
): NavButtonLayoutter {
|
||||
val navButtonContainer =
|
||||
navButtonsView.requireViewById<LinearLayout>(ID_END_NAV_BUTTONS)
|
||||
@@ -65,6 +68,7 @@ class NavButtonLayoutFactory {
|
||||
val startContextualContainer =
|
||||
navButtonsView.requireViewById<ViewGroup>(ID_START_CONTEXTUAL_BUTTONS)
|
||||
val isPhoneNavMode = phoneMode && isThreeButtonNav
|
||||
val isPhoneGestureMode = phoneMode && !isThreeButtonNav
|
||||
return when {
|
||||
isPhoneNavMode -> {
|
||||
if (!deviceProfile.isLandscape) {
|
||||
@@ -74,15 +78,30 @@ class NavButtonLayoutFactory {
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
)
|
||||
} else {
|
||||
} else if (surfaceRotation == ROTATION_90) {
|
||||
PhoneLandscapeNavLayoutter(
|
||||
resources,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
)
|
||||
} else {
|
||||
PhoneSeascapeNavLayoutter(
|
||||
resources,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
isPhoneGestureMode ->{
|
||||
PhoneGestureLayoutter(
|
||||
resources,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
)
|
||||
}
|
||||
deviceProfile.isTaskbarPresent -> {
|
||||
return when {
|
||||
isInSetup -> {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.navbutton
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import com.android.launcher3.DeviceProfile
|
||||
|
||||
/** Layoutter for showing gesture navigation on phone screen. No buttons here, no-op container */
|
||||
class PhoneGestureLayoutter(
|
||||
resources: Resources,
|
||||
navBarContainer: LinearLayout,
|
||||
endContextualContainer: ViewGroup,
|
||||
startContextualContainer: ViewGroup
|
||||
) :
|
||||
AbstractNavButtonLayoutter(
|
||||
resources,
|
||||
navBarContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
) {
|
||||
|
||||
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import com.android.launcher3.R
|
||||
import com.android.launcher3.taskbar.TaskbarManager
|
||||
import com.android.launcher3.util.DimensionUtils
|
||||
|
||||
class PhoneLandscapeNavLayoutter(
|
||||
open class PhoneLandscapeNavLayoutter(
|
||||
resources: Resources,
|
||||
navBarContainer: LinearLayout,
|
||||
endContextualContainer: ViewGroup,
|
||||
@@ -42,17 +42,15 @@ class PhoneLandscapeNavLayoutter(
|
||||
|
||||
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
|
||||
// TODO(b/230395757): Polish pending, this is just to make it usable
|
||||
val navContainerParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
|
||||
val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
|
||||
val taskbarDimensions =
|
||||
DimensionUtils.getTaskbarPhoneDimensions(dp, resources, TaskbarManager.isPhoneMode(dp))
|
||||
val taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
|
||||
TaskbarManager.isPhoneMode(dp))
|
||||
navButtonContainer.removeAllViews()
|
||||
navButtonContainer.orientation = LinearLayout.VERTICAL
|
||||
|
||||
val navContainerParams = FrameLayout.LayoutParams(
|
||||
taskbarDimensions.x, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||
navContainerParams.apply {
|
||||
width = taskbarDimensions.x
|
||||
height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
gravity = Gravity.CENTER
|
||||
topMargin = endStartMargins
|
||||
bottomMargin = endStartMargins
|
||||
marginEnd = 0
|
||||
@@ -65,6 +63,7 @@ class PhoneLandscapeNavLayoutter(
|
||||
navButtonContainer.addView(backButton)
|
||||
|
||||
navButtonContainer.layoutParams = navContainerParams
|
||||
navButtonContainer.gravity = Gravity.CENTER
|
||||
|
||||
// Add the spaces in between the nav buttons
|
||||
val spaceInBetween: Int =
|
||||
|
||||
@@ -41,27 +41,31 @@ class PhonePortraitNavLayoutter(
|
||||
|
||||
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
|
||||
// TODO(b/230395757): Polish pending, this is just to make it usable
|
||||
val navContainerParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
|
||||
val taskbarDimensions =
|
||||
DimensionUtils.getTaskbarPhoneDimensions(dp, resources, TaskbarManager.isPhoneMode(dp))
|
||||
DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
|
||||
TaskbarManager.isPhoneMode(dp))
|
||||
val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
|
||||
navContainerParams.width = taskbarDimensions.x
|
||||
navContainerParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
navContainerParams.gravity = Gravity.CENTER_VERTICAL
|
||||
|
||||
// Ensure order of buttons is correct
|
||||
navButtonContainer.removeAllViews()
|
||||
navButtonContainer.orientation = LinearLayout.HORIZONTAL
|
||||
navContainerParams.topMargin = 0
|
||||
navContainerParams.bottomMargin = 0
|
||||
navContainerParams.marginEnd = endStartMargins
|
||||
navContainerParams.marginStart = endStartMargins
|
||||
|
||||
val navContainerParams = FrameLayout.LayoutParams(
|
||||
taskbarDimensions.x, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||
navContainerParams.apply {
|
||||
topMargin = 0
|
||||
bottomMargin = 0
|
||||
marginEnd = endStartMargins
|
||||
marginStart = endStartMargins
|
||||
}
|
||||
|
||||
// Swap recents and back button in case we were landscape prior to this
|
||||
navButtonContainer.addView(backButton)
|
||||
navButtonContainer.addView(homeButton)
|
||||
navButtonContainer.addView(recentsButton)
|
||||
|
||||
navButtonContainer.layoutParams = navContainerParams
|
||||
navButtonContainer.gravity = Gravity.CENTER
|
||||
|
||||
// Add the spaces in between the nav buttons
|
||||
val spaceInBetween =
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.navbutton
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import com.android.launcher3.DeviceProfile
|
||||
|
||||
class PhoneSeascapeNavLayoutter(
|
||||
resources: Resources,
|
||||
navBarContainer: LinearLayout,
|
||||
endContextualContainer: ViewGroup,
|
||||
startContextualContainer: ViewGroup
|
||||
) :
|
||||
PhoneLandscapeNavLayoutter(
|
||||
resources,
|
||||
navBarContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
) {
|
||||
|
||||
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
|
||||
// TODO(b/230395757): Polish pending, this is just to make it usable
|
||||
super.layoutButtons(dp, isContextualButtonShowing)
|
||||
navButtonContainer.removeAllViews()
|
||||
// Flip ordering of back and recents buttons
|
||||
navButtonContainer.addView(backButton)
|
||||
navButtonContainer.addView(homeButton)
|
||||
navButtonContainer.addView(recentsButton)
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,6 @@ class TaskbarNavLayoutter(
|
||||
|
||||
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
|
||||
// Add spacing after the end of the last nav button
|
||||
val navButtonParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
|
||||
var navMarginEnd = resources.getDimension(dp.inv.inlineNavButtonsEndSpacing).toInt()
|
||||
val contextualWidth = endContextualContainer.width
|
||||
// If contextual buttons are showing, we check if the end margin is enough for the
|
||||
@@ -50,10 +49,10 @@ class TaskbarNavLayoutter(
|
||||
navMarginEnd += resources.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2
|
||||
}
|
||||
|
||||
val navButtonParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||
navButtonParams.apply {
|
||||
gravity = Gravity.END
|
||||
width = FrameLayout.LayoutParams.WRAP_CONTENT
|
||||
height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
||||
marginEnd = navMarginEnd
|
||||
}
|
||||
navButtonContainer.orientation = LinearLayout.HORIZONTAL
|
||||
|
||||
@@ -18,6 +18,8 @@ package com.android.launcher3.taskbar.overlay;
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.dot.DotInfo;
|
||||
@@ -29,12 +31,14 @@ import com.android.launcher3.taskbar.TaskbarControllers;
|
||||
import com.android.launcher3.taskbar.TaskbarDragController;
|
||||
import com.android.launcher3.taskbar.TaskbarUIController;
|
||||
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsContainerView;
|
||||
import com.android.launcher3.taskbar.allapps.TaskbarSearchSessionController;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
|
||||
|
||||
/**
|
||||
* Window context for the taskbar overlays such as All Apps and EDU.
|
||||
* <p>
|
||||
* Overlays have their own window and need a window context. Some properties are delegated to the
|
||||
* Overlays have their own window and need a window context. Some properties are
|
||||
* delegated to the
|
||||
* {@link TaskbarActivityContext} such as {@link PopupDataProvider}.
|
||||
*/
|
||||
public class TaskbarOverlayContext extends BaseTaskbarContext {
|
||||
@@ -47,6 +51,8 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
|
||||
private final int mStashedTaskbarHeight;
|
||||
private final TaskbarUIController mUiController;
|
||||
|
||||
private @Nullable TaskbarSearchSessionController mSearchSessionController;
|
||||
|
||||
public TaskbarOverlayContext(
|
||||
Context windowContext,
|
||||
TaskbarActivityContext taskbarContext,
|
||||
@@ -62,6 +68,15 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
|
||||
mUiController = controllers.uiController;
|
||||
}
|
||||
|
||||
public @Nullable TaskbarSearchSessionController getSearchSessionController() {
|
||||
return mSearchSessionController;
|
||||
}
|
||||
|
||||
public void setSearchSessionController(
|
||||
@Nullable TaskbarSearchSessionController searchSessionController) {
|
||||
mSearchSessionController = searchSessionController;
|
||||
}
|
||||
|
||||
int getStashedTaskbarHeight() {
|
||||
return mStashedTaskbarHeight;
|
||||
}
|
||||
@@ -70,7 +85,10 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
|
||||
return mOverlayController;
|
||||
}
|
||||
|
||||
/** Returns {@code true} if overlay or Taskbar windows are handling a system drag. */
|
||||
/**
|
||||
* Returns {@code true} if overlay or Taskbar windows are handling a system
|
||||
* drag.
|
||||
*/
|
||||
boolean isAnySystemDragInProgress() {
|
||||
return mDragController.isSystemDragInProgress()
|
||||
|| mTaskbarContext.getDragController().isSystemDragInProgress();
|
||||
@@ -111,6 +129,11 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
|
||||
return mTaskbarContext.getItemOnClickListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View.OnLongClickListener getAllAppsItemLongClickListener() {
|
||||
return mDragController::startDragOnLongClick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PopupDataProvider getPopupDataProvider() {
|
||||
return mTaskbarContext.getPopupDataProvider();
|
||||
@@ -127,7 +150,8 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragStart() {}
|
||||
public void onDragStart() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragEnd() {
|
||||
@@ -135,7 +159,8 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPopupVisibilityChanged(boolean isVisible) {}
|
||||
public void onPopupVisibilityChanged(boolean isVisible) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSplitScreenMenuButtonClicked() {
|
||||
|
||||
@@ -21,6 +21,7 @@ import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Insets;
|
||||
import android.media.permission.SafeCloseable;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
@@ -29,6 +30,7 @@ import android.view.WindowInsets;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.app.viewcapture.SettingsAwareViewCapture;
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
@@ -44,6 +46,7 @@ public class TaskbarOverlayDragLayer extends
|
||||
BaseDragLayer<TaskbarOverlayContext> implements
|
||||
ViewTreeObserver.OnComputeInternalInsetsListener {
|
||||
|
||||
private SafeCloseable mViewCaptureCloseable;
|
||||
private final List<OnClickListener> mOnClickListeners = new CopyOnWriteArrayList<>();
|
||||
private final TouchController mClickListenerTouchController = new TouchController() {
|
||||
@Override
|
||||
@@ -77,12 +80,15 @@ public class TaskbarOverlayDragLayer extends
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
|
||||
mViewCaptureCloseable = SettingsAwareViewCapture.getInstance(getContext())
|
||||
.startCapture(getRootView(), ".TaskbarOverlay");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
|
||||
mViewCaptureCloseable.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,16 +16,26 @@
|
||||
|
||||
package com.android.launcher3.uioverrides;
|
||||
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.Person;
|
||||
import android.content.Context;
|
||||
import android.content.pm.LauncherActivityInfo;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.window.RemoteTransition;
|
||||
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.quickstep.util.FadeOutRemoteTransition;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import app.lawnchair.LawnchairApp;
|
||||
import app.lawnchair.LawnchairAppKt;
|
||||
import app.lawnchair.util.LawnchairUtilsKt;
|
||||
|
||||
/**
|
||||
* A wrapper for the hidden API calls
|
||||
*/
|
||||
@@ -41,6 +51,22 @@ public class ApiWrapper {
|
||||
}
|
||||
|
||||
public static Map<String, LauncherActivityInfo> getActivityOverrides(Context context) {
|
||||
return context.getSystemService(LauncherApps.class).getActivityOverrides();
|
||||
return LawnchairUtilsKt.isDefaultLauncher(context) ?
|
||||
context.getSystemService(LauncherApps.class).getActivityOverrides()
|
||||
: Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ActivityOptions to play fade-out animation on closing targets
|
||||
*/
|
||||
public static ActivityOptions createFadeOutAnimOptions(Context context) {
|
||||
try {
|
||||
ActivityOptions options = ActivityOptions.makeBasic();
|
||||
options.setRemoteTransition(new RemoteTransition(new FadeOutRemoteTransition()));
|
||||
return options;
|
||||
} catch (Exception e) {
|
||||
// TODO Create our own custom closing animation
|
||||
return ActivityOptions.makeCustomAnimation(context, 0, android.R.anim.fade_out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-53
@@ -16,40 +16,34 @@
|
||||
|
||||
package com.android.launcher3.uioverrides;
|
||||
|
||||
import static com.android.app.animation.Interpolators.AGGRESSIVE_EASE_IN_OUT;
|
||||
import static com.android.app.animation.Interpolators.FINAL_FRAME;
|
||||
import static com.android.app.animation.Interpolators.INSTANT;
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME;
|
||||
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;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
|
||||
import static com.android.quickstep.views.FloatingTaskView.PRIMARY_TRANSLATE_OFFSCREEN;
|
||||
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_SECONDARY_TRANSLATION;
|
||||
import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.util.FloatProperty;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.dragndrop.DragLayer;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.statemanager.StateManager.StateHandler;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.quickstep.views.FloatingTaskView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
|
||||
/**
|
||||
@@ -113,48 +107,19 @@ public abstract class BaseRecentsViewStateController<T extends RecentsView>
|
||||
setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
|
||||
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
|
||||
|
||||
if (mRecentsView.isSplitSelectionActive()) {
|
||||
// TODO (b/238651489): Refactor state management to avoid need for double check
|
||||
FloatingTaskView floatingTask = mRecentsView.getFirstFloatingTaskView();
|
||||
if (floatingTask != null) {
|
||||
// We are in split selection state currently, transitioning to another state
|
||||
DragLayer dragLayer = mLauncher.getDragLayer();
|
||||
RectF onScreenRectF = new RectF();
|
||||
Utilities.getBoundsForViewInDragLayer(mLauncher.getDragLayer(), floatingTask,
|
||||
new Rect(0, 0, floatingTask.getWidth(), floatingTask.getHeight()),
|
||||
false, null, onScreenRectF);
|
||||
// Get the part of the floatingTask that intersects with the DragLayer (i.e. the
|
||||
// on-screen portion)
|
||||
onScreenRectF.intersect(
|
||||
dragLayer.getLeft(),
|
||||
dragLayer.getTop(),
|
||||
dragLayer.getRight(),
|
||||
dragLayer.getBottom()
|
||||
);
|
||||
|
||||
setter.setFloat(
|
||||
mRecentsView.getFirstFloatingTaskView(),
|
||||
PRIMARY_TRANSLATE_OFFSCREEN,
|
||||
mRecentsView.getPagedOrientationHandler()
|
||||
.getFloatingTaskOffscreenTranslationTarget(
|
||||
floatingTask,
|
||||
onScreenRectF,
|
||||
floatingTask.getStagePosition(),
|
||||
mLauncher.getDeviceProfile()
|
||||
),
|
||||
config.getInterpolator(
|
||||
ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN,
|
||||
LINEAR
|
||||
));
|
||||
setter.setViewAlpha(
|
||||
mRecentsView.getSplitInstructionsView(),
|
||||
0,
|
||||
config.getInterpolator(
|
||||
ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE,
|
||||
LINEAR
|
||||
)
|
||||
);
|
||||
}
|
||||
boolean exitingOverview = !FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
|
||||
&& !toState.overviewUi;
|
||||
if (mRecentsView.isSplitSelectionActive() && exitingOverview) {
|
||||
setter.add(mRecentsView.getSplitSelectController().getSplitAnimationController()
|
||||
.createPlaceholderDismissAnim(mLauncher));
|
||||
setter.setViewAlpha(
|
||||
mRecentsView.getSplitInstructionsView(),
|
||||
0,
|
||||
config.getInterpolator(
|
||||
ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE,
|
||||
LINEAR
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.uioverrides;
|
||||
|
||||
import static android.os.IBinder.FLAG_ONEWAY;
|
||||
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A binder proxy transaction listener for tracking non-whitelisted binder calls.
|
||||
*/
|
||||
public class DejankBinderTracker implements Binder.ProxyTransactListener {
|
||||
private static final String TAG = "DejankBinderTracker";
|
||||
|
||||
private static final Object sLock = new Object();
|
||||
private static final HashSet<String> sWhitelistedFrameworkClasses = new HashSet<>();
|
||||
static {
|
||||
// Common IPCs that are ok to block the main thread.
|
||||
sWhitelistedFrameworkClasses.add("android.view.IWindowSession");
|
||||
sWhitelistedFrameworkClasses.add("android.os.IPowerManager");
|
||||
}
|
||||
private static boolean sTemporarilyIgnoreTracking = false;
|
||||
|
||||
// Used by the client to limit binder tracking to specific regions
|
||||
private static boolean sTrackingAllowed = false;
|
||||
|
||||
private BiConsumer<String, Integer> mUnexpectedTransactionCallback;
|
||||
private boolean mIsTracking = false;
|
||||
|
||||
/**
|
||||
* Temporarily ignore blocking binder calls for the duration of this {@link Runnable}.
|
||||
*/
|
||||
@MainThread
|
||||
public static void whitelistIpcs(Runnable runnable) {
|
||||
sTemporarilyIgnoreTracking = true;
|
||||
runnable.run();
|
||||
sTemporarilyIgnoreTracking = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily ignore blocking binder calls for the duration of this {@link Supplier}.
|
||||
*/
|
||||
@MainThread
|
||||
public static <T> T whitelistIpcs(Supplier<T> supplier) {
|
||||
sTemporarilyIgnoreTracking = true;
|
||||
T value = supplier.get();
|
||||
sTemporarilyIgnoreTracking = false;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables binder tracking during a test.
|
||||
*/
|
||||
@MainThread
|
||||
public static void allowBinderTrackingInTests() {
|
||||
sTrackingAllowed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables binder tracking during a test.
|
||||
*/
|
||||
@MainThread
|
||||
public static void disallowBinderTrackingInTests() {
|
||||
sTrackingAllowed = false;
|
||||
}
|
||||
|
||||
public DejankBinderTracker(BiConsumer<String, Integer> unexpectedTransactionCallback) {
|
||||
mUnexpectedTransactionCallback = unexpectedTransactionCallback;
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public void startTracking() {
|
||||
if (!Build.TYPE.toLowerCase(Locale.ROOT).contains("debug")
|
||||
&& !Build.TYPE.toLowerCase(Locale.ROOT).equals("eng")) {
|
||||
Log.wtf(TAG, "Unexpected use of binder tracker in non-debug build", new Exception());
|
||||
return;
|
||||
}
|
||||
if (mIsTracking) {
|
||||
return;
|
||||
}
|
||||
mIsTracking = true;
|
||||
Binder.setProxyTransactListener(this);
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public void stopTracking() {
|
||||
if (!mIsTracking) {
|
||||
return;
|
||||
}
|
||||
mIsTracking = false;
|
||||
Binder.setProxyTransactListener(null);
|
||||
}
|
||||
|
||||
// Override the hidden Binder#onTransactStarted method
|
||||
public synchronized Object onTransactStarted(IBinder binder, int transactionCode, int flags) {
|
||||
if (!mIsTracking
|
||||
|| !sTrackingAllowed
|
||||
|| sTemporarilyIgnoreTracking
|
||||
|| (flags & FLAG_ONEWAY) == FLAG_ONEWAY
|
||||
|| !isMainThread()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String descriptor;
|
||||
try {
|
||||
descriptor = binder.getInterfaceDescriptor();
|
||||
if (sWhitelistedFrameworkClasses.contains(descriptor)) {
|
||||
return null;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
descriptor = binder.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
mUnexpectedTransactionCallback.accept(descriptor, transactionCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onTransactStarted(IBinder binder, int transactionCode) {
|
||||
// Do nothing
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransactEnded(Object session) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public static boolean isMainThread() {
|
||||
return Thread.currentThread() == Looper.getMainLooper().getThread();
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,8 @@
|
||||
*/
|
||||
package com.android.launcher3.uioverrides;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
|
||||
import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
|
||||
import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
|
||||
import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
|
||||
|
||||
import android.animation.Animator;
|
||||
@@ -95,8 +96,8 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
private Animator mSlotMachineAnim;
|
||||
private float mSlotMachineIconTranslationY;
|
||||
|
||||
private static final FloatProperty<PredictedAppIcon> SLOT_MACHINE_TRANSLATION_Y =
|
||||
new FloatProperty<PredictedAppIcon>("slotMachineTranslationY") {
|
||||
private static final FloatProperty<PredictedAppIcon> SLOT_MACHINE_TRANSLATION_Y = new FloatProperty<PredictedAppIcon>(
|
||||
"slotMachineTranslationY") {
|
||||
@Override
|
||||
public void setValue(PredictedAppIcon predictedAppIcon, float transY) {
|
||||
predictedAppIcon.mSlotMachineIconTranslationY = transY;
|
||||
@@ -118,6 +119,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
}
|
||||
|
||||
Context mContext;
|
||||
|
||||
public PredictedAppIcon(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
mContext = context;
|
||||
@@ -177,7 +179,8 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
|
||||
@Override
|
||||
public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
|
||||
// Create the slot machine animation first, since it uses the current icon to start.
|
||||
// Create the slot machine animation first, since it uses the current icon to
|
||||
// start.
|
||||
Animator slotMachineAnim = animate
|
||||
? createSlotMachineAnim(Collections.singletonList(info.bitmap), false)
|
||||
: null;
|
||||
@@ -222,7 +225,8 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
|
||||
* 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) {
|
||||
@@ -230,10 +234,14 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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
|
||||
*
|
||||
* @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,
|
||||
@@ -261,8 +269,8 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
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);
|
||||
keyframes[1].setInterpolator(ACCELERATE_DECELERATE);
|
||||
keyframes[2].setInterpolator(ACCELERATE_DECELERATE);
|
||||
|
||||
mSlotMachineAnim = ObjectAnimator.ofPropertyValuesHolder(this,
|
||||
PropertyValuesHolder.ofKeyframe(SLOT_MACHINE_TRANSLATION_Y, keyframes));
|
||||
@@ -279,7 +287,8 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
* Removes prediction ring from app icon
|
||||
*/
|
||||
public void pin(WorkspaceItemInfo info) {
|
||||
if (mIsPinned) return;
|
||||
if (mIsPinned)
|
||||
return;
|
||||
mIsPinned = true;
|
||||
applyFromWorkspaceItem(info);
|
||||
setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
|
||||
@@ -338,7 +347,6 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
if (getTag() instanceof WorkspaceItemInfo) {
|
||||
WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
|
||||
isBadged = !Process.myUserHandle().equals(info.user)
|
||||
|| info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
|
||||
|| info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,13 +43,13 @@ import dev.rikka.tools.refine.Refine;
|
||||
* Provides a Quickstep specific animation when launching an activity from an
|
||||
* app widget.
|
||||
*/
|
||||
class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
|
||||
public class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
|
||||
|
||||
private static final String TAG = "QuickstepInteractionHandler";
|
||||
|
||||
private final QuickstepLauncher mLauncher;
|
||||
|
||||
QuickstepInteractionHandler(QuickstepLauncher launcher) {
|
||||
public QuickstepInteractionHandler(QuickstepLauncher launcher) {
|
||||
mLauncher = launcher;
|
||||
}
|
||||
|
||||
@@ -97,8 +97,8 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
|
||||
}
|
||||
activityOptions.options.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR);
|
||||
activityOptions.options.setPendingIntentBackgroundActivityStartMode(
|
||||
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
|
||||
Utilities.allowBGLaunch(activityOptions.options);
|
||||
|
||||
}
|
||||
options = Pair.create(options.first, activityOptions.options);
|
||||
if (pendingIntent.isActivity()) {
|
||||
|
||||
@@ -19,23 +19,21 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
|
||||
import static android.os.Trace.TRACE_TAG_APP;
|
||||
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
|
||||
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
|
||||
|
||||
import static com.android.app.animation.Interpolators.EMPHASIZED;
|
||||
import static com.android.launcher3.LauncherSettings.Animation.DEFAULT_NO_ICON;
|
||||
import static com.android.launcher3.LauncherSettings.Animation.VIEW_BACKGROUND;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
|
||||
import static com.android.launcher3.LauncherState.ALL_APPS;
|
||||
import static com.android.launcher3.LauncherState.NORMAL;
|
||||
import static com.android.launcher3.LauncherState.NO_OFFSET;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
|
||||
import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
|
||||
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE;
|
||||
import static com.android.launcher3.config.FeatureFlags.RECEIVE_UNFOLD_EVENTS_FROM_SYSUI;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
|
||||
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
|
||||
import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
|
||||
@@ -58,8 +56,6 @@ import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SY
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityOptions;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -69,19 +65,20 @@ import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.hardware.SensorManager;
|
||||
import android.hardware.devicestate.DeviceStateManager;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.media.permission.SafeCloseable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.IBinder;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.Trace;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.RemoteAnimationTarget;
|
||||
import android.view.View;
|
||||
import android.widget.AnalogClock;
|
||||
import android.widget.TextClock;
|
||||
import android.window.BackEvent;
|
||||
import android.window.OnBackAnimationCallback;
|
||||
import android.window.OnBackInvokedDispatcher;
|
||||
@@ -106,7 +103,6 @@ import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.appprediction.PredictionRowView;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.dragndrop.DragOptions;
|
||||
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
|
||||
import com.android.launcher3.logging.InstanceId;
|
||||
import com.android.launcher3.logging.StatsLogManager;
|
||||
@@ -114,9 +110,9 @@ import com.android.launcher3.logging.StatsLogManager.StatsLogger;
|
||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||
import com.android.launcher3.model.WellbeingModel;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.proxy.ProxyActivityStarter;
|
||||
import com.android.launcher3.proxy.StartActivityParams;
|
||||
import com.android.launcher3.statehandlers.DepthController;
|
||||
import com.android.launcher3.statehandlers.DesktopVisibilityController;
|
||||
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
|
||||
@@ -148,6 +144,7 @@ import com.android.launcher3.util.RunnableList;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
|
||||
import com.android.launcher3.util.StartActivityParams;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.launcher3.widget.LauncherWidgetHolder;
|
||||
import com.android.quickstep.OverviewCommandHelper;
|
||||
@@ -155,12 +152,10 @@ import com.android.quickstep.RecentsModel;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.TaskUtils;
|
||||
import com.android.quickstep.TouchInteractionService.TISBinder;
|
||||
import com.android.quickstep.util.AsyncClockEventDelegate;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.quickstep.util.LauncherUnfoldAnimationController;
|
||||
import com.android.quickstep.util.ProxyScreenStatusProvider;
|
||||
import com.android.quickstep.util.QuickstepOnboardingPrefs;
|
||||
import com.android.quickstep.util.RemoteAnimationProvider;
|
||||
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
|
||||
import com.android.quickstep.util.SplitSelectStateController;
|
||||
import com.android.quickstep.util.SplitToWorkspaceController;
|
||||
import com.android.quickstep.util.SplitWithKeyboardShortcutController;
|
||||
@@ -170,16 +165,14 @@ import com.android.quickstep.views.FloatingTaskView;
|
||||
import com.android.quickstep.views.OverviewActionsView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.unfold.RemoteUnfoldSharedComponent;
|
||||
import com.android.systemui.unfold.UnfoldSharedComponent;
|
||||
import com.android.systemui.unfold.UnfoldTransitionFactory;
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
|
||||
import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig;
|
||||
import com.android.systemui.unfold.config.UnfoldTransitionConfig;
|
||||
import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver;
|
||||
import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider;
|
||||
import com.android.systemui.unfold.system.DeviceStateManagerFoldProvider;
|
||||
import com.android.systemui.unfold.updates.RotationChangeProvider;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
@@ -188,13 +181,21 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class QuickstepLauncher extends Launcher {
|
||||
public static final boolean ENABLE_PIP_KEEP_CLEAR_ALGORITHM = SystemProperties
|
||||
.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
|
||||
private static final boolean TRACE_LAYOUTS = SystemProperties.getBoolean("persist.debug.trace_layouts", false);
|
||||
private static final String TRACE_RELAYOUT_CLASS = SystemProperties.get("persist.debug.trace_request_layout_class",
|
||||
null);
|
||||
|
||||
private static final String TAG = "QuickstepLauncher";
|
||||
|
||||
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
|
||||
|
||||
protected static final String RING_APPEAR_ANIMATION_PREFIX = "RingAppearAnimation\t";
|
||||
|
||||
private FixedContainerItems mAllAppsPredictions;
|
||||
private HotseatPredictionController mHotseatPredictionController;
|
||||
private DepthController mDepthController;
|
||||
@@ -202,16 +203,16 @@ public class QuickstepLauncher extends Launcher {
|
||||
private QuickstepTransitionManager mAppTransitionManager;
|
||||
private OverviewActionsView mActionsView;
|
||||
private TISBindHelper mTISBindHelper;
|
||||
private @Nullable TaskbarManager mTaskbarManager;
|
||||
private @Nullable OverviewCommandHelper mOverviewCommandHelper;
|
||||
private @Nullable LauncherTaskbarUIController mTaskbarUIController;
|
||||
// Will be updated when dragging from taskbar.
|
||||
private @Nullable DragOptions mNextWorkspaceDragOptions = null;
|
||||
private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
|
||||
private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
|
||||
private SplitSelectStateController mSplitSelectStateController;
|
||||
private SplitWithKeyboardShortcutController mSplitWithKeyboardShortcutController;
|
||||
private SplitToWorkspaceController mSplitToWorkspaceController;
|
||||
|
||||
private AsyncClockEventDelegate mAsyncClockEventDelegate;
|
||||
|
||||
/**
|
||||
* If Launcher restarted while in the middle of an Overview split select, it
|
||||
* needs this data to
|
||||
@@ -242,6 +243,10 @@ public class QuickstepLauncher extends Launcher {
|
||||
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
|
||||
mDepthController = new DepthController(this);
|
||||
mDesktopVisibilityController = new DesktopVisibilityController(this);
|
||||
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
|
||||
mDesktopVisibilityController.registerSystemUiListener();
|
||||
mSplitSelectStateController.initSplitFromDesktopController(this);
|
||||
}
|
||||
mHotseatPredictionController = new HotseatPredictionController(this);
|
||||
mEnableWidgetDepth = SystemProperties.getBoolean("ro.launcher.depth.widget", true);
|
||||
getWorkspace().addOverlayCallback(
|
||||
@@ -261,7 +266,6 @@ public class QuickstepLauncher extends Launcher {
|
||||
StatsLogger logger = statsLogManager.logger().withItemInfo(info).withInstanceId(instanceId);
|
||||
if (mAllAppsPredictions != null
|
||||
&& (info.itemType == ITEM_TYPE_APPLICATION
|
||||
|| info.itemType == ITEM_TYPE_SHORTCUT
|
||||
|| info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) {
|
||||
int count = mAllAppsPredictions.items.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
@@ -331,9 +335,7 @@ public class QuickstepLauncher extends Launcher {
|
||||
mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);
|
||||
RunnableList result = super.startActivitySafely(v, intent, item);
|
||||
if (result == null) {
|
||||
if (getTaskbarUIController() == null) {
|
||||
mHotseatPredictionController.setPauseUIUpdate(false);
|
||||
}
|
||||
mHotseatPredictionController.setPauseUIUpdate(false);
|
||||
} else {
|
||||
result.add(() -> mHotseatPredictionController.setPauseUIUpdate(false));
|
||||
}
|
||||
@@ -386,7 +388,7 @@ public class QuickstepLauncher extends Launcher {
|
||||
}
|
||||
|
||||
private List<SystemShortcut.Factory<QuickstepLauncher>> getSplitShortcuts() {
|
||||
if (!mDeviceProfile.isTablet) {
|
||||
if (!mDeviceProfile.isTablet || mSplitSelectStateController.isSplitSelectActive()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
RecentsView recentsView = (RecentsView) getOverviewPanel();
|
||||
@@ -414,12 +416,8 @@ public class QuickstepLauncher extends Launcher {
|
||||
boolean visible = (state == NORMAL || state == OVERVIEW)
|
||||
&& (willUserBeActive || isUserActive())
|
||||
&& !profile.isVerticalBarLayout();
|
||||
if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
|
||||
SystemUiProxy.INSTANCE.get(this)
|
||||
.setLauncherKeepClearAreaHeight(visible, profile.hotseatBarSizePx);
|
||||
} else {
|
||||
SystemUiProxy.INSTANCE.get(this).setShelfHeight(visible, profile.hotseatBarSizePx);
|
||||
}
|
||||
SystemUiProxy.INSTANCE.get(this)
|
||||
.setLauncherKeepClearAreaHeight(visible, profile.hotseatBarSizePx);
|
||||
}
|
||||
if (state == NORMAL && !inTransition) {
|
||||
((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false);
|
||||
@@ -428,12 +426,14 @@ public class QuickstepLauncher extends Launcher {
|
||||
|
||||
@Override
|
||||
public void bindExtraContainerItems(FixedContainerItems item) {
|
||||
Log.d(TAG, "Bind extra container items");
|
||||
if (item.containerId == Favorites.CONTAINER_PREDICTION) {
|
||||
mAllAppsPredictions = item;
|
||||
PredictionRowView<?> predictionRowView = getAppsView().getFloatingHeaderView().findFixedRowByType(
|
||||
PredictionRowView.class);
|
||||
predictionRowView.setPredictedApps(item.items);
|
||||
} else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
|
||||
Log.d(TAG, "Bind extra container item is hotseat prediction");
|
||||
mHotseatPredictionController.setPredictedItems(item);
|
||||
} else if (item.containerId == Favorites.CONTAINER_WIDGETS_PREDICTION) {
|
||||
getPopupDataProvider().setRecommendedWidgets(item.items);
|
||||
@@ -451,16 +451,26 @@ public class QuickstepLauncher extends Launcher {
|
||||
mAppTransitionManager.onActivityDestroyed();
|
||||
if (mUnfoldTransitionProgressProvider != null) {
|
||||
SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(null);
|
||||
|
||||
mUnfoldTransitionProgressProvider.destroy();
|
||||
}
|
||||
mTISBindHelper.onDestroy();
|
||||
if (mTaskbarManager != null) {
|
||||
mTaskbarManager.clearActivity(this);
|
||||
}
|
||||
|
||||
if (mLauncherUnfoldAnimationController != null) {
|
||||
mLauncherUnfoldAnimationController.onDestroy();
|
||||
}
|
||||
|
||||
if (mDesktopVisibilityController != null) {
|
||||
mDesktopVisibilityController.unregisterSystemUiListener();
|
||||
}
|
||||
|
||||
if (mSplitSelectStateController != null) {
|
||||
mSplitSelectStateController.onDestroy();
|
||||
}
|
||||
|
||||
if (mAsyncClockEventDelegate != null) {
|
||||
mAsyncClockEventDelegate.onDestroy();
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
mHotseatPredictionController.destroy();
|
||||
mSplitWithKeyboardShortcutController.onDestroy();
|
||||
@@ -493,8 +503,8 @@ public class QuickstepLauncher extends Launcher {
|
||||
break;
|
||||
}
|
||||
case QUICK_SWITCH_STATE_ORDINAL: {
|
||||
RecentsView rv = (RecentsView) getOverviewPanel();
|
||||
TaskView tasktolaunch = rv.getTaskViewAt(0);
|
||||
RecentsView rv = getOverviewPanel();
|
||||
TaskView tasktolaunch = rv.getCurrentPageTaskView();
|
||||
if (tasktolaunch != null) {
|
||||
tasktolaunch.launchTask(success -> {
|
||||
if (!success) {
|
||||
@@ -516,11 +526,14 @@ public class QuickstepLauncher extends Launcher {
|
||||
NavigationMode mode = DisplayController.getNavigationMode(this);
|
||||
ArrayList<TouchController> list = new ArrayList<>();
|
||||
list.add(getDragController());
|
||||
Consumer<AnimatorSet> splitAnimator = animatorSet -> animatorSet
|
||||
.play(mSplitSelectStateController.getSplitAnimationController()
|
||||
.createPlaceholderDismissAnim(this));
|
||||
switch (mode) {
|
||||
case NO_BUTTON:
|
||||
list.add(new NoButtonQuickSwitchTouchController(this));
|
||||
list.add(new NavBarToHomeTouchController(this));
|
||||
list.add(new NoButtonNavbarToOverviewTouchController(this));
|
||||
list.add(new NavBarToHomeTouchController(this, splitAnimator));
|
||||
list.add(new NoButtonNavbarToOverviewTouchController(this, splitAnimator));
|
||||
break;
|
||||
case TWO_BUTTONS:
|
||||
list.add(new TwoButtonNavbarTouchController(this));
|
||||
@@ -530,8 +543,14 @@ public class QuickstepLauncher extends Launcher {
|
||||
list.add(new PortraitStatesTouchController(this));
|
||||
break;
|
||||
case THREE_BUTTONS:
|
||||
list.add(new NoButtonQuickSwitchTouchController(this));
|
||||
list.add(new NavBarToHomeTouchController(this, splitAnimator));
|
||||
list.add(new NoButtonNavbarToOverviewTouchController(this, splitAnimator));
|
||||
list.add(new PortraitStatesTouchController(this));
|
||||
break;
|
||||
default:
|
||||
list.add(new PortraitStatesTouchController(this));
|
||||
break;
|
||||
}
|
||||
if (!getDeviceProfile().isMultiWindowMode) {
|
||||
list.add(new StatusBarTouchController(this));
|
||||
@@ -570,6 +589,10 @@ public class QuickstepLauncher extends Launcher {
|
||||
mViewCapture = SettingsAwareViewCapture.getInstance(this).startCapture(getWindow());
|
||||
}
|
||||
getWindow().addPrivateFlags(PRIVATE_FLAG_OPTIMIZE_MEASURE);
|
||||
if (Utilities.ATLEAST_U) {
|
||||
View.setTraceLayoutSteps(TRACE_LAYOUTS);
|
||||
View.setTracedRequestLayoutClassClass(TRACE_RELAYOUT_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -578,12 +601,15 @@ public class QuickstepLauncher extends Launcher {
|
||||
// Check if there is already an instance of this app running, if so, initiate
|
||||
// the split
|
||||
// using that.
|
||||
mSplitSelectStateController.findLastActiveTaskAndRunCallback(
|
||||
splitSelectSource.itemInfo.getComponentKey(),
|
||||
foundTask -> {
|
||||
splitSelectSource.alreadyRunningTaskId = foundTask == null
|
||||
? INVALID_TASK_ID
|
||||
: foundTask.key.id;
|
||||
mSplitSelectStateController.findLastActiveTasksAndRunCallback(
|
||||
Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()),
|
||||
foundTasks -> {
|
||||
@Nullable
|
||||
Task foundTask = foundTasks.get(0);
|
||||
boolean taskWasFound = foundTask != null;
|
||||
splitSelectSource.alreadyRunningTaskId = taskWasFound
|
||||
? foundTask.key.id
|
||||
: INVALID_TASK_ID;
|
||||
if (ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
|
||||
startSplitToHome(splitSelectSource);
|
||||
} else {
|
||||
@@ -622,9 +648,13 @@ public class QuickstepLauncher extends Launcher {
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
getDragLayer().removeView(floatingTaskView);
|
||||
mSplitSelectStateController.getSplitAnimationController()
|
||||
.removeSplitInstructionsView(QuickstepLauncher.this);
|
||||
mSplitSelectStateController.resetState();
|
||||
}
|
||||
});
|
||||
anim.add(mSplitSelectStateController.getSplitAnimationController()
|
||||
.getShowSplitInstructionsAnim(this).buildAnim());
|
||||
anim.buildAnim().start();
|
||||
}
|
||||
|
||||
@@ -647,8 +677,9 @@ public class QuickstepLauncher extends Launcher {
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
if (mOverviewCommandHelper != null) {
|
||||
mOverviewCommandHelper.clearPendingCommands();
|
||||
OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper();
|
||||
if (overviewCommandHelper != null) {
|
||||
overviewCommandHelper.clearPendingCommands();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -774,8 +805,9 @@ public class QuickstepLauncher extends Launcher {
|
||||
}
|
||||
|
||||
private void onTaskbarInAppDisplayProgressUpdate(float progress, int flag) {
|
||||
if (mTaskbarManager == null
|
||||
|| mTaskbarManager.getCurrentActivityContext() == null
|
||||
TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
|
||||
if (taskbarManager == null
|
||||
|| taskbarManager.getCurrentActivityContext() == null
|
||||
|| mTaskbarUIController == null) {
|
||||
return;
|
||||
}
|
||||
@@ -819,7 +851,7 @@ public class QuickstepLauncher extends Launcher {
|
||||
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
|
||||
DesktopVisibilityController controller = mDesktopVisibilityController;
|
||||
if (controller != null && controller.areFreeformTasksVisible()
|
||||
&& !controller.isGestureInProgress()) {
|
||||
&& !controller.isRecentsGestureInProgress()) {
|
||||
// Return early to skip setting activity to appear as resumed
|
||||
// TODO(b/255649902): shouldn't be needed when we have a separate launcher state
|
||||
// for desktop that we can use to control other parts of launcher
|
||||
@@ -849,11 +881,10 @@ public class QuickstepLauncher extends Launcher {
|
||||
}
|
||||
|
||||
private void onTISConnected(TISBinder binder) {
|
||||
mTaskbarManager = binder.getTaskbarManager();
|
||||
if (mTaskbarManager != null) {
|
||||
mTaskbarManager.setActivity(this);
|
||||
TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
|
||||
if (taskbarManager != null) {
|
||||
taskbarManager.setActivity(this);
|
||||
}
|
||||
mOverviewCommandHelper = binder.getOverviewCommandHelper();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -864,41 +895,10 @@ public class QuickstepLauncher extends Launcher {
|
||||
private void initUnfoldTransitionProgressProvider() {
|
||||
final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
|
||||
if (config.isEnabled()) {
|
||||
if (RECEIVE_UNFOLD_EVENTS_FROM_SYSUI.get()) {
|
||||
initRemotelyCalculatedUnfoldAnimation(config);
|
||||
} else {
|
||||
initLocallyCalculatedUnfoldAnimation(config);
|
||||
}
|
||||
initRemotelyCalculatedUnfoldAnimation(config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers hinge angle listener and calculates the animation progress in this
|
||||
* process.
|
||||
*/
|
||||
private void initLocallyCalculatedUnfoldAnimation(UnfoldTransitionConfig config) {
|
||||
UnfoldSharedComponent unfoldComponent = UnfoldTransitionFactory.createUnfoldSharedComponent(
|
||||
/* context= */ this,
|
||||
config,
|
||||
ProxyScreenStatusProvider.INSTANCE,
|
||||
new DeviceStateManagerFoldProvider(
|
||||
getSystemService(DeviceStateManager.class), /* context= */ this),
|
||||
new ActivityManagerActivityTypeProvider(
|
||||
getSystemService(ActivityManager.class)),
|
||||
getSystemService(SensorManager.class),
|
||||
getMainThreadHandler(),
|
||||
getMainExecutor(),
|
||||
/* backgroundExecutor= */ UI_HELPER_EXECUTOR,
|
||||
/* tracingTagPrefix= */ "launcher",
|
||||
getSystemService(DisplayManager.class));
|
||||
mUnfoldTransitionProgressProvider = unfoldComponent.getUnfoldTransitionProvider()
|
||||
.orElseThrow(() -> new IllegalStateException(
|
||||
"Trying to create UnfoldTransitionProgressProvider when the "
|
||||
+ "transition is disabled"));
|
||||
initUnfoldAnimationController(mUnfoldTransitionProgressProvider,
|
||||
unfoldComponent.getRotationChangeProvider());
|
||||
}
|
||||
|
||||
/** Receives animation progress from sysui process. */
|
||||
private void initRemotelyCalculatedUnfoldAnimation(UnfoldTransitionConfig config) {
|
||||
RemoteUnfoldSharedComponent unfoldComponent = UnfoldTransitionFactory.createRemoteUnfoldSharedComponent(
|
||||
@@ -942,6 +942,13 @@ public class QuickstepLauncher extends Launcher {
|
||||
return mSplitToWorkspaceController;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleSplitAnimationGoingToHome() {
|
||||
super.handleSplitAnimationGoingToHome();
|
||||
mSplitSelectStateController.getSplitAnimationController()
|
||||
.playPlaceholderDismissAnim(this);
|
||||
}
|
||||
|
||||
public <T extends OverviewActionsView> T getActionsView() {
|
||||
return (T) mActionsView;
|
||||
}
|
||||
@@ -977,39 +984,6 @@ public class QuickstepLauncher extends Launcher {
|
||||
return mAppTransitionManager.hasControlRemoteAppTransitionPermission();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DragOptions getDefaultWorkspaceDragOptions() {
|
||||
if (mNextWorkspaceDragOptions != null) {
|
||||
DragOptions options = mNextWorkspaceDragOptions;
|
||||
mNextWorkspaceDragOptions = null;
|
||||
return options;
|
||||
}
|
||||
return super.getDefaultWorkspaceDragOptions();
|
||||
}
|
||||
|
||||
public void setNextWorkspaceDragOptions(DragOptions dragOptions) {
|
||||
mNextWorkspaceDragOptions = dragOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
|
||||
QuickstepTransitionManager appTransitionManager = getAppTransitionManager();
|
||||
appTransitionManager.setRemoteAnimationProvider(new RemoteAnimationProvider() {
|
||||
@Override
|
||||
public AnimatorSet createWindowAnimation(RemoteAnimationTarget[] appTargets,
|
||||
RemoteAnimationTarget[] wallpaperTargets) {
|
||||
// On the first call clear the reference.
|
||||
signal.cancel();
|
||||
ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0);
|
||||
fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets,
|
||||
wallpaperTargets));
|
||||
AnimatorSet anim = new AnimatorSet();
|
||||
anim.play(fadeAnimation);
|
||||
return anim;
|
||||
}
|
||||
}, signal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] getNormalOverviewScaleAndOffset() {
|
||||
return DisplayController.getNavigationMode(this).hasGestures
|
||||
@@ -1064,6 +1038,7 @@ public class QuickstepLauncher extends Launcher {
|
||||
(v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId()
|
||||
: Display.DEFAULT_DISPLAY);
|
||||
if (app.lawnchair.LawnchairApp.isAtleastT()) {
|
||||
Utilities.allowBGLaunch(activityOptions.options);
|
||||
addLaunchCookie(item, activityOptions.options);
|
||||
}
|
||||
return activityOptions;
|
||||
@@ -1077,6 +1052,7 @@ public class QuickstepLauncher extends Launcher {
|
||||
Executors.MAIN_EXECUTOR.getHandler(), null,
|
||||
elapsedRealTime -> callbacks.executeAllAndDestroy());
|
||||
options.setSplashScreenStyle(splashScreenStyle);
|
||||
Utilities.allowBGLaunch(options);
|
||||
return new ActivityOptionsWrapper(options, callbacks);
|
||||
}
|
||||
|
||||
@@ -1126,7 +1102,6 @@ public class QuickstepLauncher extends Launcher {
|
||||
}
|
||||
switch (info.itemType) {
|
||||
case Favorites.ITEM_TYPE_APPLICATION:
|
||||
case Favorites.ITEM_TYPE_SHORTCUT:
|
||||
case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
|
||||
case Favorites.ITEM_TYPE_APPWIDGET:
|
||||
// Fall through and continue if it's an app, shortcut, or widget
|
||||
@@ -1242,8 +1217,9 @@ public class QuickstepLauncher extends Launcher {
|
||||
public void dispatchDeviceProfileChanged() {
|
||||
super.dispatchDeviceProfileChanged();
|
||||
SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
|
||||
if (mTaskbarManager != null) {
|
||||
mTaskbarManager.debugWhyTaskbarNotDestroyed("QuickstepLauncher#onDeviceProfileChanged");
|
||||
TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
|
||||
if (taskbarManager != null) {
|
||||
taskbarManager.debugWhyTaskbarNotDestroyed("QuickstepLauncher#onDeviceProfileChanged");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1264,8 +1240,7 @@ public class QuickstepLauncher extends Launcher {
|
||||
groupTask.task1.key.id,
|
||||
groupTask.task2.key.id,
|
||||
SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
|
||||
/* callback= */ success -> {
|
||||
},
|
||||
/* callback= */ success -> mSplitSelectStateController.resetState(),
|
||||
/* freezeTaskList= */ true,
|
||||
groupTask.mSplitBounds == null
|
||||
? DEFAULT_SPLIT_RATIO
|
||||
@@ -1274,6 +1249,18 @@ public class QuickstepLauncher extends Launcher {
|
||||
: groupTask.mSplitBounds.leftTaskPercent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches two apps as an app pair.
|
||||
*/
|
||||
public void launchAppPair(WorkspaceItemInfo app1, WorkspaceItemInfo app2) {
|
||||
mSplitSelectStateController.getAppPairsController().launchAppPair(app1, app2);
|
||||
}
|
||||
|
||||
public boolean canStartHomeSafely() {
|
||||
OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper();
|
||||
return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely();
|
||||
}
|
||||
|
||||
private static final class LauncherTaskViewController extends
|
||||
TaskViewTouchController<Launcher> {
|
||||
LauncherTaskViewController(Launcher activity) {
|
||||
@@ -1309,5 +1296,34 @@ public class QuickstepLauncher extends Launcher {
|
||||
if (recentsView != null) {
|
||||
recentsView.getSplitSelectController().dump(prefix, writer);
|
||||
}
|
||||
if (mAppTransitionManager != null) {
|
||||
mAppTransitionManager.dump(prefix + "\t" + RING_APPEAR_ANIMATION_PREFIX, writer);
|
||||
}
|
||||
if (mHotseatPredictionController != null) {
|
||||
mHotseatPredictionController.dump(prefix, writer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
|
||||
switch (name) {
|
||||
case "TextClock", "android.widget.TextClock" -> {
|
||||
TextClock tc = new TextClock(context, attrs);
|
||||
if (mAsyncClockEventDelegate == null) {
|
||||
mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
|
||||
}
|
||||
// tc.setClockEventDelegate(mAsyncClockEventDelegate);
|
||||
return tc;
|
||||
}
|
||||
case "AnalogClock", "android.widget.AnalogClock" -> {
|
||||
AnalogClock ac = new AnalogClock(context, attrs);
|
||||
if (mAsyncClockEventDelegate == null) {
|
||||
mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
|
||||
}
|
||||
// ac.setClockEventDelegate(mAsyncClockEventDelegate);
|
||||
return ac;
|
||||
}
|
||||
}
|
||||
return super.onCreateView(parent, name, context, attrs);
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ import java.util.function.IntConsumer;
|
||||
/**
|
||||
* {@link LauncherWidgetHolder} that puts the app widget host in the background
|
||||
*/
|
||||
public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
|
||||
public class QuickstepWidgetHolder extends LauncherWidgetHolder {
|
||||
|
||||
private static final String TAG = "QuickstepWidgetHolder";
|
||||
|
||||
@@ -76,7 +76,7 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
|
||||
// Map to all pending updated keyed with appWidgetId;
|
||||
private final SparseArray<PendingUpdate> mPendingUpdateMap = new SparseArray<>();
|
||||
|
||||
private QuickstepWidgetHolder(@NonNull Context context,
|
||||
public QuickstepWidgetHolder(@NonNull Context context,
|
||||
@Nullable IntConsumer appWidgetRemovedCallback,
|
||||
@Nullable RemoteViews.InteractionHandler interactionHandler) {
|
||||
super(context, appWidgetRemovedCallback);
|
||||
@@ -243,6 +243,7 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
|
||||
} else {
|
||||
widgetView = new LauncherAppWidgetHostView(context);
|
||||
}
|
||||
widgetView.setIsWidgetCachingDisabled(true);
|
||||
widgetView.setInteractionHandler(mInteractionHandler);
|
||||
widgetView.setAppWidget(appWidgetId, appWidget);
|
||||
mViews.put(appWidgetId, widgetView);
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
*/
|
||||
package com.android.launcher3.uioverrides;
|
||||
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
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.anim.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
|
||||
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
|
||||
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
|
||||
|
||||
@@ -23,6 +23,7 @@ import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
|
||||
import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
|
||||
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
|
||||
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
|
||||
@@ -59,6 +60,7 @@ import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceGroup;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
import androidx.preference.SeekBarPreference;
|
||||
import androidx.preference.SwitchPreference;
|
||||
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
@@ -106,6 +108,9 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
|
||||
loadPluginPrefs();
|
||||
maybeAddSandboxCategory();
|
||||
addOnboardingPrefsCatergory();
|
||||
if (FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()) {
|
||||
addAllAppsFromOverviewCatergory();
|
||||
}
|
||||
|
||||
if (getActivity() != null) {
|
||||
getActivity().setTitle("Developer Options");
|
||||
@@ -393,6 +398,33 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
|
||||
}
|
||||
}
|
||||
|
||||
private void addAllAppsFromOverviewCatergory() {
|
||||
PreferenceCategory category = newCategory("All Apps from Overview Config");
|
||||
|
||||
SeekBarPreference thresholdPref = new SeekBarPreference(getContext());
|
||||
thresholdPref.setTitle("Threshold to open All Apps from Overview");
|
||||
thresholdPref.setSingleLineTitle(false);
|
||||
|
||||
// These values are 100x swipe up shift value (100 = where overview sits).
|
||||
thresholdPref.setMax(500);
|
||||
thresholdPref.setMin(105);
|
||||
thresholdPref.setUpdatesContinuously(true);
|
||||
thresholdPref.setIconSpaceReserved(false);
|
||||
// Don't directly save to shared prefs, use LauncherPrefs instead.
|
||||
thresholdPref.setPersistent(false);
|
||||
thresholdPref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
LauncherPrefs.get(getContext()).put(ALL_APPS_OVERVIEW_THRESHOLD, newValue);
|
||||
preference.setSummary(String.valueOf((int) newValue / 100f));
|
||||
return true;
|
||||
});
|
||||
int value = LauncherPrefs.get(getContext()).get(ALL_APPS_OVERVIEW_THRESHOLD);
|
||||
thresholdPref.setValue(value);
|
||||
// For some reason the initial value is not triggering the summary update, so call manually.
|
||||
thresholdPref.getOnPreferenceChangeListener().onPreferenceChange(thresholdPref, value);
|
||||
|
||||
category.addPreference(thresholdPref);
|
||||
}
|
||||
|
||||
private String toName(String action) {
|
||||
String str = action.replace("com.android.systemui.action.PLUGIN_", "")
|
||||
.replace("com.android.launcher3.action.PLUGIN_", "");
|
||||
|
||||
@@ -15,14 +15,16 @@
|
||||
*/
|
||||
package com.android.launcher3.uioverrides.states;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
|
||||
import static com.android.app.animation.Interpolators.DECELERATE_2;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.util.Themes;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
|
||||
@@ -94,7 +96,7 @@ public class AllAppsState extends LauncherState {
|
||||
@Override
|
||||
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
|
||||
PageAlphaProvider superPageAlphaProvider = super.getWorkspacePageAlphaProvider(launcher);
|
||||
return new PageAlphaProvider(DEACCEL_2) {
|
||||
return new PageAlphaProvider(DECELERATE_2) {
|
||||
@Override
|
||||
public float getPageAlpha(int pageIndex) {
|
||||
return launcher.getDeviceProfile().isTablet
|
||||
@@ -106,14 +108,52 @@ public class AllAppsState extends LauncherState {
|
||||
|
||||
@Override
|
||||
public int getVisibleElements(Launcher launcher) {
|
||||
// Don't add HOTSEAT_ICONS for non-tablets in ALL_APPS state.
|
||||
return launcher.getDeviceProfile().isTablet ? ALL_APPS_CONTENT | HOTSEAT_ICONS
|
||||
: ALL_APPS_CONTENT;
|
||||
int elements = ALL_APPS_CONTENT | FLOATING_SEARCH_BAR;
|
||||
// Only add HOTSEAT_ICONS for tablets in ALL_APPS state.
|
||||
if (launcher.getDeviceProfile().isTablet) {
|
||||
elements |= HOTSEAT_ICONS;
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFloatingSearchBarRestingMarginBottom(Launcher launcher) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFloatingSearchBarRestingMarginStart(Launcher launcher) {
|
||||
DeviceProfile dp = launcher.getDeviceProfile();
|
||||
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFloatingSearchBarRestingMarginEnd(Launcher launcher) {
|
||||
DeviceProfile dp = launcher.getDeviceProfile();
|
||||
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldFloatingSearchBarUsePillWhenUnfocused(Launcher launcher) {
|
||||
DeviceProfile dp = launcher.getDeviceProfile();
|
||||
return dp.isPhone && !dp.isLandscape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LauncherState getHistoryForState(LauncherState previousState) {
|
||||
return previousState == OVERVIEW ? OVERVIEW : NORMAL;
|
||||
return previousState == BACKGROUND_APP ? QUICK_SWITCH_FROM_HOME
|
||||
: previousState == OVERVIEW ? OVERVIEW : NORMAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] getOverviewScaleAndOffset(Launcher launcher) {
|
||||
if (!FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()) {
|
||||
return super.getOverviewScaleAndOffset(launcher);
|
||||
}
|
||||
// This handles the case of returning to the previous app from Overview -> All Apps gesture.
|
||||
// This is the start scale/offset of overview that will be used for that transition.
|
||||
// TODO (b/283336332): Translate in Y direction (ideally with overview resistance).
|
||||
return new float[] {0.5f /* scale */, NO_OFFSET};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package com.android.launcher3.uioverrides.states;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
|
||||
import static com.android.app.animation.Interpolators.DECELERATE_2;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -102,7 +102,7 @@ public class OverviewState extends LauncherState {
|
||||
|
||||
@Override
|
||||
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
|
||||
return new PageAlphaProvider(DEACCEL_2) {
|
||||
return new PageAlphaProvider(DECELERATE_2) {
|
||||
@Override
|
||||
public float getPageAlpha(int pageIndex) {
|
||||
return 0;
|
||||
@@ -115,7 +115,32 @@ public class OverviewState extends LauncherState {
|
||||
if (PreferenceManager.getInstance(launcher).getRecentsActionClearAll().get()) {
|
||||
return OVERVIEW_ACTIONS;
|
||||
}
|
||||
return CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
|
||||
int elements = CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
|
||||
DeviceProfile dp = launcher.getDeviceProfile();
|
||||
boolean showFloatingSearch;
|
||||
if (dp.isPhone) {
|
||||
// Only show search in phone overview in portrait mode.
|
||||
showFloatingSearch = !dp.isLandscape;
|
||||
} else {
|
||||
// Only show search in tablet overview if taskbar is not visible.
|
||||
showFloatingSearch = !dp.isTaskbarPresent || isTaskbarStashed(launcher);
|
||||
}
|
||||
if (showFloatingSearch) {
|
||||
elements |= FLOATING_SEARCH_BAR;
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFloatingSearchBarRestingMarginBottom(Launcher launcher) {
|
||||
return areElementsVisible(launcher, FLOATING_SEARCH_BAR) ? 0
|
||||
: super.getFloatingSearchBarRestingMarginBottom(launcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldFloatingSearchBarUsePillWhenUnfocused(Launcher launcher) {
|
||||
DeviceProfile dp = launcher.getDeviceProfile();
|
||||
return dp.isPhone && !dp.isLandscape;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+35
-29
@@ -17,6 +17,20 @@ package com.android.launcher3.uioverrides.states;
|
||||
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import static com.android.app.animation.Interpolators.ACCELERATE;
|
||||
import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
|
||||
import static com.android.app.animation.Interpolators.DECELERATE;
|
||||
import static com.android.app.animation.Interpolators.DECELERATE_1_7;
|
||||
import static com.android.app.animation.Interpolators.DECELERATE_3;
|
||||
import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
|
||||
import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
|
||||
import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
|
||||
import static com.android.app.animation.Interpolators.FINAL_FRAME;
|
||||
import static com.android.app.animation.Interpolators.INSTANT;
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.app.animation.Interpolators.OVERSHOOT_0_75;
|
||||
import static com.android.app.animation.Interpolators.OVERSHOOT_1_2;
|
||||
import static com.android.app.animation.Interpolators.clampToProgress;
|
||||
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;
|
||||
@@ -25,20 +39,6 @@ import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
|
||||
import static com.android.launcher3.QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION;
|
||||
import static com.android.launcher3.WorkspaceStateTransitionAnimation.getWorkspaceSpringScaleAnimator;
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL;
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL;
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
|
||||
import static com.android.launcher3.anim.Interpolators.EMPHASIZED_ACCELERATE;
|
||||
import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE;
|
||||
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
|
||||
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.anim.Interpolators.OVERSHOOT_0_75;
|
||||
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
|
||||
import static com.android.launcher3.anim.Interpolators.clampToProgress;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
|
||||
@@ -55,12 +55,14 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_T
|
||||
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.CellLayout;
|
||||
import com.android.launcher3.Hotseat;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.touch.AllAppsSwipeController;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
@@ -78,12 +80,14 @@ public class QuickstepAtomicAnimationFactory extends
|
||||
private static final float RECENTS_PREPARE_SCALE = 1.33f;
|
||||
// Scale workspace takes before animating in
|
||||
private static final float WORKSPACE_PREPARE_SCALE = 0.92f;
|
||||
// Constants to specify how to scroll RecentsView to the default page if it's not already there.
|
||||
// Constants to specify how to scroll RecentsView to the default page if it's
|
||||
// not already there.
|
||||
private static final int DEFAULT_PAGE = 0;
|
||||
private static final int PER_PAGE_SCROLL_DURATION = 150;
|
||||
private static final int MAX_PAGE_SCROLL_DURATION = 750;
|
||||
|
||||
// Due to use of physics, duration may differ between devices so we need to calculate and
|
||||
// Due to use of physics, duration may differ between devices so we need to
|
||||
// calculate and
|
||||
// cache the value.
|
||||
private int mHintToNormalDuration = -1;
|
||||
|
||||
@@ -94,11 +98,11 @@ public class QuickstepAtomicAnimationFactory extends
|
||||
@Override
|
||||
public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
|
||||
StateAnimationConfig config) {
|
||||
|
||||
Log.d(TestProtocol.OVERVIEW_OVER_HOME, "creating animation fromState: "
|
||||
+ fromState + " toState: " + toState);
|
||||
RecentsView overview = mActivity.getOverviewPanel();
|
||||
if ((fromState == OVERVIEW || fromState == OVERVIEW_SPLIT_SELECT) && toState == NORMAL) {
|
||||
overview.switchToScreenshot(() ->
|
||||
overview.finishRecentsAnimation(true /* toRecents */, null));
|
||||
overview.switchToScreenshot(() -> overview.finishRecentsAnimation(true /* toRecents */, null));
|
||||
|
||||
if (fromState == OVERVIEW_SPLIT_SELECT) {
|
||||
config.setInterpolator(ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN,
|
||||
@@ -112,8 +116,8 @@ public class QuickstepAtomicAnimationFactory extends
|
||||
fromState == OVERVIEW_SPLIT_SELECT
|
||||
? clampToProgress(LINEAR, 0.33f, 1)
|
||||
: LINEAR);
|
||||
config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
|
||||
config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
|
||||
config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATE);
|
||||
config.setInterpolator(ANIM_WORKSPACE_FADE, ACCELERATE);
|
||||
|
||||
if (DisplayController.getNavigationMode(mActivity).hasGestures
|
||||
&& overview.getTaskViewCount() > 0) {
|
||||
@@ -132,20 +136,22 @@ public class QuickstepAtomicAnimationFactory extends
|
||||
numPagesToScroll * PER_PAGE_SCROLL_DURATION);
|
||||
config.duration = Math.max(config.duration, scrollDuration);
|
||||
|
||||
// Sync scroll so that it ends before or at the same time as the taskbar animation.
|
||||
// Sync scroll so that it ends before or at the same time as the taskbar
|
||||
// animation.
|
||||
if (DisplayController.isTransientTaskbar(mActivity)
|
||||
&& mActivity.getDeviceProfile().isTaskbarPresent) {
|
||||
config.duration = Math.min(config.duration, TASKBAR_TO_HOME_DURATION);
|
||||
}
|
||||
overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration));
|
||||
} else {
|
||||
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL_DEACCEL);
|
||||
config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
|
||||
config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
|
||||
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCELERATE_DECELERATE);
|
||||
config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCELERATE, 0, 0.9f));
|
||||
config.setInterpolator(ANIM_OVERVIEW_FADE, DECELERATE_1_7);
|
||||
}
|
||||
|
||||
Workspace<?> workspace = mActivity.getWorkspace();
|
||||
// Start from a higher workspace scale, but only if we're invisible so we don't jump.
|
||||
// Start from a higher workspace scale, but only if we're invisible so we don't
|
||||
// jump.
|
||||
boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
|
||||
if (isWorkspaceVisible) {
|
||||
CellLayout currentChild = (CellLayout) workspace.getChildAt(
|
||||
@@ -167,8 +173,8 @@ public class QuickstepAtomicAnimationFactory extends
|
||||
|| fromState == HINT_STATE_TWO_BUTTON) && toState == OVERVIEW) {
|
||||
if (DisplayController.getNavigationMode(mActivity).hasGestures) {
|
||||
config.setInterpolator(ANIM_WORKSPACE_SCALE,
|
||||
fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
|
||||
config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
|
||||
fromState == NORMAL ? ACCELERATE : OVERSHOOT_1_2);
|
||||
config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCELERATE);
|
||||
|
||||
// Scrolling in tasks, so show straight away
|
||||
if (overview.getTaskViewCount() > 0) {
|
||||
@@ -196,7 +202,7 @@ public class QuickstepAtomicAnimationFactory extends
|
||||
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_2);
|
||||
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_2);
|
||||
} else if (fromState == HINT_STATE && toState == NORMAL) {
|
||||
config.setInterpolator(ANIM_DEPTH, DEACCEL_3);
|
||||
config.setInterpolator(ANIM_DEPTH, DECELERATE_3);
|
||||
if (mHintToNormalDuration == -1) {
|
||||
ValueAnimator va = getWorkspaceSpringScaleAnimator(mActivity,
|
||||
mActivity.getWorkspace(),
|
||||
|
||||
+25
-4
@@ -15,19 +15,22 @@
|
||||
*/
|
||||
package com.android.launcher3.uioverrides.touchcontrollers;
|
||||
|
||||
import static com.android.app.animation.Interpolators.DECELERATE_3;
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
|
||||
import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
|
||||
import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
|
||||
import static com.android.launcher3.LauncherState.ALL_APPS;
|
||||
import static com.android.launcher3.LauncherState.NORMAL;
|
||||
import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
|
||||
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_ALPHA;
|
||||
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PULL_BACK_TRANSLATION;
|
||||
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
|
||||
import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
|
||||
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.animation.Interpolator;
|
||||
@@ -41,24 +44,29 @@ import com.android.launcher3.allapps.AllAppsTransitionController;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.compat.AccessibilityManagerCompat;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.touch.SingleAxisSwipeDetector;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.quickstep.TaskUtils;
|
||||
import com.android.quickstep.util.AnimatorControllerWithResistance;
|
||||
import com.android.quickstep.util.OverviewToHomeAnim;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps.
|
||||
*/
|
||||
public class NavBarToHomeTouchController implements TouchController,
|
||||
SingleAxisSwipeDetector.Listener {
|
||||
|
||||
private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3;
|
||||
private static final Interpolator PULLBACK_INTERPOLATOR = DECELERATE_3;
|
||||
// The min amount of overview scrim we keep during the transition.
|
||||
private static final float OVERVIEW_TO_HOME_SCRIM_MULTIPLIER = 0.5f;
|
||||
|
||||
private final Launcher mLauncher;
|
||||
private final Consumer<AnimatorSet> mCancelSplitRunnable;
|
||||
private final SingleAxisSwipeDetector mSwipeDetector;
|
||||
private final float mPullbackDistance;
|
||||
|
||||
@@ -67,8 +75,14 @@ public class NavBarToHomeTouchController implements TouchController,
|
||||
private LauncherState mEndState = NORMAL;
|
||||
private AnimatorPlaybackController mCurrentAnimation;
|
||||
|
||||
public NavBarToHomeTouchController(Launcher launcher) {
|
||||
/**
|
||||
* @param cancelSplitRunnable Called when split placeholder view needs to be cancelled.
|
||||
* Animation should be added to the provided AnimatorSet
|
||||
*/
|
||||
public NavBarToHomeTouchController(Launcher launcher,
|
||||
Consumer<AnimatorSet> cancelSplitRunnable) {
|
||||
mLauncher = launcher;
|
||||
mCancelSplitRunnable = cancelSplitRunnable;
|
||||
mSwipeDetector = new SingleAxisSwipeDetector(mLauncher, this,
|
||||
SingleAxisSwipeDetector.VERTICAL);
|
||||
mPullbackDistance = mLauncher.getResources().getDimension(R.dimen.home_pullback_distance);
|
||||
@@ -95,6 +109,10 @@ public class NavBarToHomeTouchController implements TouchController,
|
||||
}
|
||||
|
||||
private boolean canInterceptTouch(MotionEvent ev) {
|
||||
if (!isTrackpadMotionEvent(ev) && DisplayController.getNavigationMode(mLauncher)
|
||||
== THREE_BUTTONS) {
|
||||
return false;
|
||||
}
|
||||
boolean cameFromNavBar = (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0;
|
||||
if (!cameFromNavBar) {
|
||||
return false;
|
||||
@@ -176,7 +194,10 @@ public class NavBarToHomeTouchController implements TouchController,
|
||||
recentsView.switchToScreenshot(null,
|
||||
() -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
|
||||
if (mStartState.overviewUi) {
|
||||
new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState))
|
||||
new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState),
|
||||
FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
|
||||
? mCancelSplitRunnable
|
||||
: null)
|
||||
.animateWithVelocity(velocity);
|
||||
} else {
|
||||
mLauncher.getStateManager().goToState(mEndState, true,
|
||||
|
||||
+24
-7
@@ -16,19 +16,22 @@
|
||||
|
||||
package com.android.launcher3.uioverrides.touchcontrollers;
|
||||
|
||||
import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
|
||||
import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
|
||||
import static com.android.launcher3.LauncherState.ALL_APPS;
|
||||
import static com.android.launcher3.LauncherState.HINT_STATE;
|
||||
import static com.android.launcher3.LauncherState.NORMAL;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
|
||||
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.NavigationMode.THREE_BUTTONS;
|
||||
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.graphics.PointF;
|
||||
@@ -43,6 +46,7 @@ import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.VibratorWrapper;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.util.AnimatorControllerWithResistance;
|
||||
@@ -50,6 +54,8 @@ import com.android.quickstep.util.MotionPauseDetector;
|
||||
import com.android.quickstep.util.OverviewToHomeAnim;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Touch controller which handles swipe and hold from the nav bar to go to Overview. Swiping above
|
||||
* the nav bar falls back to go to All Apps. Swiping from the nav bar without holding goes to the
|
||||
@@ -64,6 +70,7 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
|
||||
private static final float TRANSLATION_ANIM_VELOCITY_DP_PER_MS = 0.8f;
|
||||
|
||||
private final VibratorWrapper mVibratorWrapper;
|
||||
private final Consumer<AnimatorSet> mCancelSplitRunnable;
|
||||
private final RecentsView mRecentsView;
|
||||
private final MotionPauseDetector mMotionPauseDetector;
|
||||
private final float mMotionPauseMinDisplacement;
|
||||
@@ -79,16 +86,26 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
|
||||
// Normal to Hint animation has flag SKIP_OVERVIEW, so we update this scrim with this animator.
|
||||
private ObjectAnimator mNormalToHintOverviewScrimAnimator;
|
||||
|
||||
public NoButtonNavbarToOverviewTouchController(Launcher l) {
|
||||
/**
|
||||
* @param cancelSplitRunnable Called when split placeholder view needs to be cancelled.
|
||||
* Animation should be added to the provided AnimatorSet
|
||||
*/
|
||||
public NoButtonNavbarToOverviewTouchController(Launcher l,
|
||||
Consumer<AnimatorSet> cancelSplitRunnable) {
|
||||
super(l);
|
||||
mRecentsView = l.getOverviewPanel();
|
||||
mMotionPauseDetector = new MotionPauseDetector(l);
|
||||
mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
|
||||
mVibratorWrapper = VibratorWrapper.INSTANCE.get(l.getApplicationContext());
|
||||
mCancelSplitRunnable = cancelSplitRunnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canInterceptTouch(MotionEvent ev) {
|
||||
if (!isTrackpadMotionEvent(ev) && DisplayController.getNavigationMode(mLauncher)
|
||||
== THREE_BUTTONS) {
|
||||
return false;
|
||||
}
|
||||
mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
|
||||
boolean isOneHandedModeActive = (SystemUiProxy.INSTANCE.get(mLauncher)
|
||||
.getLastSystemUiStateFlags() & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
|
||||
@@ -102,9 +119,6 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
|
||||
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
|
||||
if (fromState == NORMAL && mDidTouchStartInNavBar) {
|
||||
return HINT_STATE;
|
||||
} else if (fromState == OVERVIEW && isDragTowardPositive) {
|
||||
// Don't allow swiping up to all apps.
|
||||
return OVERVIEW;
|
||||
}
|
||||
return super.getTargetState(fromState, isDragTowardPositive);
|
||||
}
|
||||
@@ -190,6 +204,9 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
|
||||
// state, but since the hint state tracks the entire screen without a clear endpoint, we
|
||||
// need to manually set the duration to a reasonable value.
|
||||
animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher, true /* isToState */));
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
mCancelSplitRunnable.accept(animatorSet);
|
||||
animatorSet.start();
|
||||
}
|
||||
if (FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get() &&
|
||||
((mFromState == NORMAL && mToState == ALL_APPS)
|
||||
@@ -261,7 +278,7 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
|
||||
private void goToOverviewOrHomeOnDragEnd(float velocity) {
|
||||
boolean goToHomeInsteadOfOverview = !mMotionPauseDetector.isPaused();
|
||||
if (goToHomeInsteadOfOverview) {
|
||||
new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(NORMAL))
|
||||
new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(NORMAL), null)
|
||||
.animateWithVelocity(velocity);
|
||||
}
|
||||
if (mReachedOverview) {
|
||||
@@ -273,7 +290,7 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch
|
||||
mRecentsView.animate()
|
||||
.translationX(0)
|
||||
.translationY(0)
|
||||
.setInterpolator(ACCEL_DEACCEL)
|
||||
.setInterpolator(ACCELERATE_DECELERATE)
|
||||
.setDuration(duration)
|
||||
.withEndAction(goToHomeInsteadOfOverview
|
||||
? null
|
||||
|
||||
+51
-27
@@ -18,19 +18,20 @@ package com.android.launcher3.uioverrides.touchcontrollers;
|
||||
import static android.view.MotionEvent.ACTION_DOWN;
|
||||
import static android.view.MotionEvent.ACTION_MOVE;
|
||||
|
||||
import static com.android.app.animation.Interpolators.ACCELERATE_0_75;
|
||||
import static com.android.app.animation.Interpolators.DECELERATE_3;
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.app.animation.Interpolators.scrollInterpolatorForVelocity;
|
||||
import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
|
||||
import static com.android.launcher3.LauncherState.NORMAL;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
|
||||
import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME;
|
||||
import static com.android.launcher3.MotionEventsUtils.isTrackpadFourFingerSwipe;
|
||||
import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
|
||||
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
|
||||
import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
|
||||
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEDOWN;
|
||||
@@ -46,6 +47,7 @@ import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
|
||||
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.NavigationMode.THREE_BUTTONS;
|
||||
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
|
||||
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
|
||||
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
|
||||
@@ -74,6 +76,7 @@ import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.launcher3.touch.BaseSwipeDetector;
|
||||
import com.android.launcher3.touch.BothAxesSwipeDetector;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.launcher3.util.VibratorWrapper;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
@@ -84,17 +87,20 @@ import com.android.quickstep.util.WorkspaceRevealAnim;
|
||||
import com.android.quickstep.views.DesktopTaskView;
|
||||
import com.android.quickstep.views.LauncherRecentsView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
|
||||
|
||||
/**
|
||||
* Handles quick switching to a recent task from the home screen. To give as much flexibility to
|
||||
* the user as possible, also handles swipe up and hold to go to overview and swiping back home.
|
||||
* Handles quick switching to a recent task from the home screen. To give as
|
||||
* much flexibility to
|
||||
* the user as possible, also handles swipe up and hold to go to overview and
|
||||
* swiping back home.
|
||||
*/
|
||||
public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
BothAxesSwipeDetector.Listener {
|
||||
|
||||
private static final float Y_ANIM_MIN_PROGRESS = 0.25f;
|
||||
private static final Interpolator FADE_OUT_INTERPOLATOR = DEACCEL_3;
|
||||
private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCEL_0_75;
|
||||
private static final Interpolator FADE_OUT_INTERPOLATOR = DECELERATE_3;
|
||||
private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCELERATE_0_75;
|
||||
private static final Interpolator SCALE_DOWN_INTERPOLATOR = LINEAR;
|
||||
private static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
|
||||
|
||||
@@ -106,8 +112,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
private final MotionPauseDetector mMotionPauseDetector;
|
||||
private final float mMotionPauseMinDisplacement;
|
||||
private final LauncherRecentsView mRecentsView;
|
||||
protected final AnimatorListener mClearStateOnCancelListener =
|
||||
newCancelListener(this::clearState);
|
||||
protected final AnimatorListener mClearStateOnCancelListener = newCancelListener(this::clearState);
|
||||
|
||||
private boolean mNoIntercept;
|
||||
private Boolean mIsTrackpadFourFingerSwipe;
|
||||
@@ -115,7 +120,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
|
||||
private boolean mIsHomeScreenVisible = true;
|
||||
|
||||
// As we drag, we control 3 animations: one to get non-overview components out of the way,
|
||||
// As we drag, we control 3 animations: one to get non-overview components out
|
||||
// of the way,
|
||||
// and the other two to set overview properties based on x and y progress.
|
||||
private AnimatorPlaybackController mNonOverviewAnim;
|
||||
private AnimatorPlaybackController mXOverviewAnim;
|
||||
@@ -127,7 +133,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
mRecentsView = mLauncher.getOverviewPanel();
|
||||
mXRange = mLauncher.getDeviceProfile().widthPx / 2f;
|
||||
mYRange = LayoutUtils.getShelfTrackingDistance(
|
||||
mLauncher, mLauncher.getDeviceProfile(), mRecentsView.getPagedOrientationHandler());
|
||||
mLauncher, mLauncher.getDeviceProfile(), mRecentsView.getPagedOrientationHandler());
|
||||
mMaxYProgress = mLauncher.getDeviceProfile().heightPx / mYRange;
|
||||
mMotionPauseDetector = new MotionPauseDetector(mLauncher);
|
||||
mMotionPauseMinDisplacement = mLauncher.getResources().getDimension(
|
||||
@@ -136,19 +142,18 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
|
||||
@Override
|
||||
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
|
||||
int action = ev.getActionMasked();
|
||||
if (action == ACTION_DOWN) {
|
||||
mIsTrackpadFourFingerSwipe = null;
|
||||
if (ev.getActionMasked() == ACTION_DOWN) {
|
||||
mNoIntercept = !canInterceptTouch(ev);
|
||||
if (mNoIntercept) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only detect horizontal swipe for intercept, then we will allow swipe up as well.
|
||||
// Only detect horizontal swipe for intercept, then we will allow swipe up as
|
||||
// well.
|
||||
mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT,
|
||||
false /* ignoreSlopWhenSettling */);
|
||||
} else if (isTrackpadMultiFingerSwipe(ev) && mIsTrackpadFourFingerSwipe == null
|
||||
&& action == ACTION_MOVE) {
|
||||
&& ev.getActionMasked() == ACTION_MOVE) {
|
||||
mIsTrackpadFourFingerSwipe = isTrackpadFourFingerSwipe(ev);
|
||||
mNoIntercept = !mIsTrackpadFourFingerSwipe;
|
||||
if (mNoIntercept) {
|
||||
@@ -170,6 +175,9 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
}
|
||||
|
||||
private boolean canInterceptTouch(MotionEvent ev) {
|
||||
if (!isTrackpadMotionEvent(ev) && DisplayController.getNavigationMode(mLauncher) == THREE_BUTTONS) {
|
||||
return false;
|
||||
}
|
||||
if (!mLauncher.isInState(LauncherState.NORMAL)) {
|
||||
return false;
|
||||
}
|
||||
@@ -184,6 +192,9 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
// TODO(b/268075592): add support for quickswitch to/from desktop
|
||||
return false;
|
||||
}
|
||||
if (isTrackpadMultiFingerSwipe(ev)) {
|
||||
return isTrackpadFourFingerSwipe(ev);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -191,6 +202,9 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
public void onDragStart(boolean start) {
|
||||
mMotionPauseDetector.clear();
|
||||
if (start) {
|
||||
InteractionJankMonitorWrapper.begin(mRecentsView,
|
||||
InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
|
||||
|
||||
mStartState = mLauncher.getStateManager().getState();
|
||||
|
||||
mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseDetected);
|
||||
@@ -255,9 +269,9 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
|
||||
float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
|
||||
// As we drag right, animate the following properties:
|
||||
// - RecentsView translationX
|
||||
// - OverviewScrim
|
||||
// - RecentsView fade (if it's empty)
|
||||
// - RecentsView translationX
|
||||
// - OverviewScrim
|
||||
// - 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,
|
||||
@@ -271,8 +285,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
mXOverviewAnim.dispatchOnStart();
|
||||
|
||||
// As we drag up, animate the following properties:
|
||||
// - RecentsView scale
|
||||
// - RecentsView fullscreenProgress
|
||||
// - RecentsView scale
|
||||
// - RecentsView fullscreenProgress
|
||||
PendingAnimation yAnim = new PendingAnimation((long) (mYRange * 2));
|
||||
yAnim.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
|
||||
SCALE_DOWN_INTERPOLATOR);
|
||||
@@ -302,8 +316,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
if (wasHomeScreenVisible && mNonOverviewAnim != null) {
|
||||
mNonOverviewAnim.setPlayFraction(xProgress);
|
||||
}
|
||||
mIsHomeScreenVisible = FADE_OUT_INTERPOLATOR.getInterpolation(xProgress)
|
||||
<= 1 - ALPHA_CUTOFF_THRESHOLD;
|
||||
mIsHomeScreenVisible = FADE_OUT_INTERPOLATOR.getInterpolation(xProgress) <= 1 - ALPHA_CUTOFF_THRESHOLD;
|
||||
|
||||
mMotionPauseDetector.setDisallowPause(-displacement.y < mMotionPauseMinDisplacement);
|
||||
mMotionPauseDetector.addPosition(ev);
|
||||
@@ -325,6 +338,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
if (mMotionPauseDetector.isPaused() && noFling) {
|
||||
// Going to Overview.
|
||||
cancelAnimations();
|
||||
InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
|
||||
|
||||
StateAnimationConfig config = new StateAnimationConfig();
|
||||
config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
|
||||
@@ -340,7 +354,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
});
|
||||
overviewAnim.start();
|
||||
|
||||
// Create an empty state transition so StateListeners get onStateTransitionStart().
|
||||
// Create an empty state transition so StateListeners get
|
||||
// onStateTransitionStart().
|
||||
mLauncher.getStateManager().createAnimationToNewWorkspace(
|
||||
OVERVIEW, config.duration, StateAnimationConfig.SKIP_ALL_ANIMATIONS)
|
||||
.dispatchOnStart();
|
||||
@@ -360,7 +375,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
// Flinging up and right could go either home or to quick switch.
|
||||
// Determine the target based on the higher velocity.
|
||||
targetState = Math.abs(velocity.x) > Math.abs(velocity.y)
|
||||
? QUICK_SWITCH_FROM_HOME : NORMAL;
|
||||
? QUICK_SWITCH_FROM_HOME
|
||||
: NORMAL;
|
||||
}
|
||||
}
|
||||
} else if (horizontalFling) {
|
||||
@@ -372,7 +388,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
boolean passedHorizontalThreshold = mXOverviewAnim.getInterpolatedProgress() > 0.5f;
|
||||
boolean passedVerticalThreshold = mYOverviewAnim.value > 1f;
|
||||
targetState = passedHorizontalThreshold && !passedVerticalThreshold
|
||||
? QUICK_SWITCH_FROM_HOME : NORMAL;
|
||||
? QUICK_SWITCH_FROM_HOME
|
||||
: NORMAL;
|
||||
}
|
||||
|
||||
// Animate the various components to the target state.
|
||||
@@ -441,6 +458,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
RecentsView.SCROLL_VIBRATION_PRIMITIVE,
|
||||
RecentsView.SCROLL_VIBRATION_PRIMITIVE_SCALE,
|
||||
RecentsView.SCROLL_VIBRATION_FALLBACK);
|
||||
} else {
|
||||
InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
|
||||
}
|
||||
|
||||
nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
|
||||
@@ -462,6 +481,11 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
|
||||
: targetState.ordinal > mStartState.ordinal
|
||||
? LAUNCHER_UNKNOWN_SWIPEUP
|
||||
: LAUNCHER_UNKNOWN_SWIPEDOWN));
|
||||
|
||||
if (targetState == QUICK_SWITCH_FROM_HOME) {
|
||||
InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
|
||||
}
|
||||
|
||||
mLauncher.getStateManager().goToState(targetState, false, forEndCallback(this::clearState));
|
||||
}
|
||||
|
||||
|
||||
+5
-4
@@ -24,11 +24,12 @@ import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.allapps.AllAppsTransitionController;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.launcher3.touch.AbstractStateChangeTouchController;
|
||||
import com.android.launcher3.touch.AllAppsSwipeController;
|
||||
@@ -92,9 +93,9 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
|
||||
@Override
|
||||
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
|
||||
if (fromState == ALL_APPS && !isDragTowardPositive) {
|
||||
return NORMAL;
|
||||
} else if (fromState == OVERVIEW) {
|
||||
return isDragTowardPositive ? OVERVIEW : NORMAL;
|
||||
return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()
|
||||
? mLauncher.getStateManager().getLastState()
|
||||
: NORMAL;
|
||||
} else if (fromState == NORMAL && isDragTowardPositive) {
|
||||
return ALL_APPS;
|
||||
}
|
||||
|
||||
+10
-10
@@ -15,12 +15,12 @@
|
||||
*/
|
||||
package com.android.launcher3.uioverrides.touchcontrollers;
|
||||
|
||||
import static com.android.app.animation.Interpolators.ACCELERATE_2;
|
||||
import static com.android.app.animation.Interpolators.DECELERATE_2;
|
||||
import static com.android.app.animation.Interpolators.INSTANT;
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.LauncherState.NORMAL;
|
||||
import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME;
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
|
||||
import static com.android.launcher3.anim.Interpolators.INSTANT;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
|
||||
@@ -128,14 +128,14 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll
|
||||
}
|
||||
|
||||
private void setupInterpolators(StateAnimationConfig stateAnimationConfig) {
|
||||
stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_2);
|
||||
stateAnimationConfig.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_2);
|
||||
stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_FADE, DECELERATE_2);
|
||||
stateAnimationConfig.setInterpolator(ANIM_ALL_APPS_FADE, DECELERATE_2);
|
||||
if (DisplayController.getNavigationMode(mLauncher) == NavigationMode.NO_BUTTON) {
|
||||
// Overview lives to the left of workspace, so translate down later than over
|
||||
stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL_2);
|
||||
stateAnimationConfig.setInterpolator(ANIM_VERTICAL_PROGRESS, ACCEL_2);
|
||||
stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_SCALE, ACCEL_2);
|
||||
stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, ACCEL_2);
|
||||
stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCELERATE_2);
|
||||
stateAnimationConfig.setInterpolator(ANIM_VERTICAL_PROGRESS, ACCELERATE_2);
|
||||
stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_SCALE, ACCELERATE_2);
|
||||
stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, ACCELERATE_2);
|
||||
stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
|
||||
} else {
|
||||
stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_TRANSLATE, LINEAR);
|
||||
|
||||
+1
-28
@@ -92,32 +92,7 @@ public class StatusBarTouchController implements TouchController {
|
||||
private void dispatchTouchEvent(MotionEvent ev) {
|
||||
if (mSystemUiProxy.isActive()) {
|
||||
mLastAction = ev.getActionMasked();
|
||||
mSystemUiProxy.onStatusBarMotionEvent(ev);
|
||||
} else if (!mExpanded) {
|
||||
mExpanded = true;
|
||||
expand();
|
||||
}
|
||||
if (!mVibrated) {
|
||||
mVibrated = true;
|
||||
vibrate();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint({ "WrongConstant", "PrivateApi" })
|
||||
private void expand() {
|
||||
try {
|
||||
Class.forName("android.app.StatusBarManager")
|
||||
.getMethod("expandNotificationsPanel")
|
||||
.invoke(mLauncher.getSystemService("statusbar"));
|
||||
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException
|
||||
| ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void vibrate() {
|
||||
if (!LawnchairAppKt.getLawnchairApp(mLauncher).isVibrateOnIconAnimation()) {
|
||||
VibratorWrapper.INSTANCE.get(mLauncher).vibrate(VibratorWrapper.OVERVIEW_HAPTIC);
|
||||
mSystemUiProxy.onStatusBarTouchEvent(ev);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,8 +106,6 @@ public class StatusBarTouchController implements TouchController {
|
||||
if (!mCanIntercept) {
|
||||
return false;
|
||||
}
|
||||
mExpanded = false;
|
||||
mVibrated = false;
|
||||
mDownEvents.clear();
|
||||
mDownEvents.put(pid, new PointF(ev.getX(), ev.getY()));
|
||||
} else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
|
||||
|
||||
+1
-1
@@ -27,13 +27,13 @@ import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.LauncherAnimUtils;
|
||||
import com.android.launcher3.R;
|
||||
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.touch.BaseSwipeDetector;
|
||||
import com.android.launcher3.touch.PagedOrientationHandler;
|
||||
|
||||
@@ -21,19 +21,19 @@ import static android.view.Surface.ROTATION_270;
|
||||
import static android.view.Surface.ROTATION_90;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
|
||||
import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
|
||||
import static com.android.app.animation.Interpolators.DECELERATE;
|
||||
import static com.android.app.animation.Interpolators.OVERSHOOT_1_2;
|
||||
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
|
||||
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
|
||||
import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
|
||||
import static com.android.launcher3.PagedView.INVALID_PAGE;
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL;
|
||||
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
|
||||
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.uioverrides.QuickstepLauncher.ENABLE_PIP_KEEP_CLEAR_ALGORITHM;
|
||||
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;
|
||||
@@ -94,6 +94,7 @@ import androidx.annotation.UiThread;
|
||||
import com.android.internal.util.LatencyTracker;
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimationSuccessListener;
|
||||
@@ -102,14 +103,17 @@ import com.android.launcher3.dragndrop.DragView;
|
||||
import com.android.launcher3.logging.StatsLogManager;
|
||||
import com.android.launcher3.statehandlers.DepthController;
|
||||
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
|
||||
import com.android.launcher3.statehandlers.DesktopVisibilityController;
|
||||
import com.android.launcher3.statemanager.BaseState;
|
||||
import com.android.launcher3.statemanager.StatefulActivity;
|
||||
import com.android.launcher3.taskbar.TaskbarThresholdUtils;
|
||||
import com.android.launcher3.taskbar.TaskbarUIController;
|
||||
import com.android.launcher3.tracing.InputConsumerProto;
|
||||
import com.android.launcher3.tracing.SwipeHandlerProto;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.util.TraceHelper;
|
||||
import com.android.launcher3.util.VibratorWrapper;
|
||||
import com.android.launcher3.util.WindowBounds;
|
||||
@@ -123,7 +127,6 @@ import com.android.quickstep.util.AnimatorControllerWithResistance;
|
||||
import com.android.quickstep.util.InputConsumerProxy;
|
||||
import com.android.quickstep.util.InputProxyHandlerFactory;
|
||||
import com.android.quickstep.util.MotionPauseDetector;
|
||||
import com.android.quickstep.util.ProtoTracer;
|
||||
import com.android.quickstep.util.RecentsOrientedState;
|
||||
import com.android.quickstep.util.RectFSpringAnim;
|
||||
import com.android.quickstep.util.StaggeredWorkspaceAnim;
|
||||
@@ -134,6 +137,7 @@ import com.android.quickstep.util.TaskViewSimulator;
|
||||
import com.android.quickstep.views.DesktopTaskView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
@@ -150,6 +154,7 @@ import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import app.lawnchair.util.CompatibilityKt;
|
||||
@@ -188,18 +193,19 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
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;
|
||||
}
|
||||
ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED);
|
||||
mRecentsView = null;
|
||||
mActivity = null;
|
||||
}
|
||||
};
|
||||
|
||||
private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks =
|
||||
new ActivityLifecycleCallbacksAdapter() {
|
||||
@Override
|
||||
public void onActivityDestroyed(Activity activity) {
|
||||
if (mActivity != activity) {
|
||||
return;
|
||||
}
|
||||
ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED);
|
||||
mRecentsView = null;
|
||||
mActivity = null;
|
||||
mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
|
||||
}
|
||||
};
|
||||
private static int FLAG_COUNT = 0;
|
||||
|
||||
private static int getNextStateFlag(String name) {
|
||||
@@ -284,7 +290,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
|
||||
private boolean mContinuingLastGesture;
|
||||
|
||||
private ThumbnailData mTaskSnapshot;
|
||||
// Cache of recently-updated task snapshots, mapping task id to ThumbnailData
|
||||
private HashMap<Integer, ThumbnailData> mTaskSnapshotCache = new HashMap<>();
|
||||
|
||||
// Used to control launcher components throughout the swipe gesture.
|
||||
private AnimatorControllerWithResistance mLauncherTransitionController;
|
||||
@@ -369,12 +376,12 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
TaskbarUIController controller = mActivityInterface.getTaskbarController();
|
||||
mTaskbarAlreadyOpen = controller != null && !controller.isTaskbarStashed();
|
||||
mIsTaskbarAllAppsOpen = controller != null && controller.isTaskbarAllAppsOpen();
|
||||
mTaskbarAppWindowThreshold = res.getDimensionPixelSize(R.dimen.taskbar_app_window_threshold);
|
||||
mTaskbarAppWindowThreshold = TaskbarThresholdUtils.getAppWindowThreshold(res, mDp);
|
||||
boolean swipeWillNotShowTaskbar = mTaskbarAlreadyOpen || mGestureState.isTrackpadGesture();
|
||||
mTaskbarHomeOverviewThreshold = swipeWillNotShowTaskbar
|
||||
? 0
|
||||
: res.getDimensionPixelSize(R.dimen.taskbar_home_overview_threshold);
|
||||
mTaskbarCatchUpThreshold = res.getDimensionPixelSize(R.dimen.taskbar_catch_up_threshold);
|
||||
: TaskbarThresholdUtils.getHomeOverviewThreshold(res, mDp);
|
||||
mTaskbarCatchUpThreshold = TaskbarThresholdUtils.getCatchUpThreshold(res, mDp);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -510,6 +517,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
// view when
|
||||
// the animation is canceled
|
||||
mGestureState.runOnceAtState(STATE_RECENTS_ANIMATION_CANCELED, () -> {
|
||||
if (mRecentsView == null)
|
||||
return;
|
||||
|
||||
HashMap<Integer, ThumbnailData> snapshots = mGestureState.consumeRecentsAnimationCanceledSnapshot();
|
||||
if (snapshots != null) {
|
||||
mRecentsView.switchToScreenshot(snapshots, () -> {
|
||||
@@ -521,9 +531,6 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
}
|
||||
});
|
||||
mRecentsView.onRecentsAnimationComplete();
|
||||
if (mRecentsAnimationController != null) {
|
||||
mRecentsAnimationController.cleanupScreenshot();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -586,7 +593,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
if (mWasLauncherAlreadyVisible) {
|
||||
mStateCallback.setState(STATE_LAUNCHER_DRAWN);
|
||||
} else {
|
||||
Object traceToken = TraceHelper.INSTANCE.beginSection("WTS-init");
|
||||
SafeCloseable traceToken = TraceHelper.INSTANCE.beginAsyncSection("WTS-init");
|
||||
View dragLayer = activity.getDragLayer();
|
||||
dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
|
||||
boolean mHandled = false;
|
||||
@@ -598,7 +605,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
}
|
||||
mHandled = true;
|
||||
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
traceToken.close();
|
||||
dragLayer.post(() -> dragLayer.getViewTreeObserver().removeOnDrawListener(this));
|
||||
if (activity != mActivity) {
|
||||
return;
|
||||
@@ -657,11 +664,22 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
|
||||
protected void notifyGestureAnimationStartToRecents() {
|
||||
Task[] runningTasks;
|
||||
TopTaskTracker.CachedTaskInfo cachedTaskInfo = mGestureState.getRunningTask();
|
||||
if (mIsSwipeForSplit) {
|
||||
int[] splitTaskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds();
|
||||
runningTasks = mGestureState.getRunningTask().getPlaceholderTasks(splitTaskIds);
|
||||
runningTasks = cachedTaskInfo.getPlaceholderTasks(splitTaskIds);
|
||||
} else {
|
||||
runningTasks = mGestureState.getRunningTask().getPlaceholderTasks();
|
||||
runningTasks = cachedTaskInfo.getPlaceholderTasks();
|
||||
}
|
||||
|
||||
// Safeguard against any null tasks being sent to recents view, happens when
|
||||
// quickswitching
|
||||
// very quickly w/ split tasks because TopTaskTracker provides stale information
|
||||
// compared to
|
||||
// actual running tasks in the recents animation.
|
||||
// TODO(b/236226779), Proper fix (ag/22237143)
|
||||
if (Arrays.stream(runningTasks).anyMatch(Objects::isNull)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Safeguard against any null tasks being sent to recents view, happens when
|
||||
@@ -683,11 +701,10 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
private void initializeLauncherAnimationController() {
|
||||
buildAnimationController();
|
||||
|
||||
Object traceToken = TraceHelper.INSTANCE.beginSection("logToggleRecents",
|
||||
TraceHelper.FLAG_IGNORE_BINDERS);
|
||||
LatencyTracker.getInstance(mContext).logAction(LatencyTracker.ACTION_TOGGLE_RECENTS,
|
||||
(int) (mLauncherFrameDrawnTime - mTouchTimeMs));
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
try (SafeCloseable c = TraceHelper.INSTANCE.allowIpcs("logToggleRecents")) {
|
||||
LatencyTracker.getInstance(mContext).logAction(LatencyTracker.ACTION_TOGGLE_RECENTS,
|
||||
(int) (mLauncherFrameDrawnTime - mTouchTimeMs));
|
||||
}
|
||||
|
||||
// This method is only called when STATE_GESTURE_STARTED is set, so we can
|
||||
// enable the
|
||||
@@ -739,11 +756,15 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
* @param moveRunningTask whether to move running task to front when attaching
|
||||
*/
|
||||
private void maybeUpdateRecentsAttachedState(boolean animate, boolean moveRunningTask) {
|
||||
if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) {
|
||||
if ((!mDeviceState.isFullyGesturalNavMode() && !mGestureState.isTrackpadGesture())
|
||||
|| mRecentsView == null) {
|
||||
return;
|
||||
}
|
||||
// looking at single target is fine here since either app of a split pair would
|
||||
// have their "isInRecents" field set? (that's what this is used for below)
|
||||
RemoteAnimationTarget runningTaskTarget = mRecentsAnimationTargets != null
|
||||
? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
|
||||
? mRecentsAnimationTargets
|
||||
.findTask(mGestureState.getTopRunningTaskId())
|
||||
: null;
|
||||
final boolean recentsAttachedToAppWindow;
|
||||
if (mIsInAllAppsRegion) {
|
||||
@@ -822,6 +843,10 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
|
||||
maybeUpdateRecentsAttachedState(true);
|
||||
|
||||
if (mActivity != null) {
|
||||
mActivity.getAppsView().getSearchUiManager().prepareToFocusEditText(mIsInAllAppsRegion);
|
||||
}
|
||||
|
||||
// Draw active task below Launcher so that All Apps can appear over it.
|
||||
runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
|
||||
.setDrawsBelowRecents(isInAllAppsRegion));
|
||||
@@ -891,7 +916,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
@UiThread
|
||||
@Override
|
||||
public void onCurrentShiftUpdated() {
|
||||
setIsInAllAppsRegion(mCurrentShift.value >= ALL_APPS_SHIFT_THRESHOLD);
|
||||
float threshold = LauncherPrefs.get(mContext).get(ALL_APPS_OVERVIEW_THRESHOLD) / 100f;
|
||||
setIsInAllAppsRegion(mCurrentShift.value >= threshold);
|
||||
updateSysUiFlags(mCurrentShift.value);
|
||||
applyScrollAndTransform();
|
||||
|
||||
@@ -1180,6 +1206,14 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
|
||||
// Notify the SysUI to use fade-in animation when entering PiP
|
||||
SystemUiProxy.INSTANCE.get(mContext).setPipAnimationTypeToAlpha();
|
||||
if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
|
||||
// Notify the SysUI to stash desktop apps if they are visible
|
||||
DesktopVisibilityController desktopVisibilityController = mActivityInterface
|
||||
.getDesktopVisibilityController();
|
||||
if (desktopVisibilityController != null) {
|
||||
desktopVisibilityController.onHomeActionTriggered();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RECENTS:
|
||||
mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
|
||||
@@ -1213,7 +1247,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
return false;
|
||||
}
|
||||
boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTarget).anyMatch(
|
||||
targetCompat -> targetCompat.taskId == mGestureState.getLastStartedTaskId());
|
||||
mGestureState.mLastStartedTaskIdPredicate);
|
||||
if (mStateCallback.hasStates(STATE_START_NEW_TASK) && hasStartedTaskBefore) {
|
||||
reset();
|
||||
return true;
|
||||
@@ -1360,11 +1394,11 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
Interpolator interpolator;
|
||||
S state = mActivityInterface.stateFromGestureEndTarget(endTarget);
|
||||
if (state.displayOverviewTasksAsGrid(mDp)) {
|
||||
interpolator = ACCEL_DEACCEL;
|
||||
interpolator = ACCELERATE_DECELERATE;
|
||||
} else if (endTarget == RECENTS) {
|
||||
interpolator = OVERSHOOT_1_2;
|
||||
} else {
|
||||
interpolator = DEACCEL;
|
||||
interpolator = DECELERATE;
|
||||
}
|
||||
|
||||
if (endTarget.isLauncher) {
|
||||
@@ -1479,22 +1513,13 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
ArrayList<IBinder> launchCookies, long duration, boolean isTargetTranslucent,
|
||||
boolean appCanEnterPip, RemoteAnimationTarget runningTaskTarget);
|
||||
|
||||
private final TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
|
||||
@Override
|
||||
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
|
||||
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
|
||||
if (task.taskId == mGestureState.getRunningTaskId()
|
||||
&& task.configuration.windowConfiguration.getActivityType() != ACTIVITY_TYPE_HOME) {
|
||||
// Since this is an edge case, just cancel and relaunch with default activity
|
||||
// options (since we don't know if there's an associated app icon to launch
|
||||
// from)
|
||||
endRunningWindowAnim(true /* cancel */);
|
||||
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
|
||||
mActivityRestartListener);
|
||||
ActivityManagerWrapper.getInstance().startActivityFromRecents(task.taskId, null);
|
||||
}
|
||||
}
|
||||
};
|
||||
private final TaskStackChangeListener mActivityRestartListener=new TaskStackChangeListener(){@Override public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,boolean homeTaskVisible,boolean clearedTask,boolean wasVisible){boolean taskRunningAndNotHome=Arrays.stream(mGestureState.getRunningTaskIds(true /*
|
||||
* getMultipleTasks
|
||||
*/)).anyMatch(taskId->task.taskId==taskId&&task.configuration.windowConfiguration.getActivityType()!=ACTIVITY_TYPE_HOME);if(taskRunningAndNotHome){
|
||||
// Since this is an edge case, just cancel and relaunch with default activity
|
||||
// options (since we don't know if there's an associated app icon to launch
|
||||
// from)
|
||||
endRunningWindowAnim(true /* cancel */);TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mActivityRestartListener);ActivityManagerWrapper.getInstance().startActivityFromRecents(task.taskId,null);}}};
|
||||
|
||||
@UiThread
|
||||
private void animateToProgressInternal(float start, float end, long duration,
|
||||
@@ -1529,8 +1554,12 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
|
||||
if (mGestureState.getEndTarget() == HOME) {
|
||||
getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
|
||||
// Take first task ID, if there are multiple we don't have any special home
|
||||
// animation so doesn't matter for splitscreen.. though the "allowEnterPip" might change
|
||||
// depending on which task it is..
|
||||
final RemoteAnimationTarget runningTaskTarget = mRecentsAnimationTargets != null
|
||||
? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
|
||||
? mRecentsAnimationTargets
|
||||
.findTask(mGestureState.getTopRunningTaskId())
|
||||
: null;
|
||||
final ArrayList<IBinder> cookies = runningTaskTarget != null
|
||||
&& runningTaskTarget.taskInfo != null
|
||||
@@ -1558,11 +1587,12 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
mSwipePipToHomeReleaseCheck.setCanRelease(false);
|
||||
}
|
||||
|
||||
// grab a screenshot before the PipContentOverlay gets parented on top of the
|
||||
// task
|
||||
// grab a screenshot before the PipContentOverlay gets parented on top of the task
|
||||
UI_HELPER_EXECUTOR.execute(() -> {
|
||||
mTaskSnapshot = mRecentsAnimationController.screenshotTask(
|
||||
mGestureState.getRunningTaskId());
|
||||
// Directly use top task, split to pip handled on shell side
|
||||
final int taskId = mGestureState.getTopRunningTaskId();
|
||||
mTaskSnapshotCache.put(taskId,
|
||||
mRecentsAnimationController.screenshotTask(taskId));
|
||||
});
|
||||
|
||||
// let SystemUi reparent the overlay leash as soon as possible
|
||||
@@ -1582,7 +1612,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
|
||||
|
||||
windowAnim[0].addAnimatorListener(new AnimationSuccessListener() {
|
||||
@Override
|
||||
|
||||
@Override
|
||||
public void onAnimationSuccess(Animator animator) {
|
||||
if (mRecentsAnimationController == null) {
|
||||
// If the recents animation is interrupted, we still end the running
|
||||
@@ -1778,11 +1809,6 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
|
||||
private Rect getKeepClearAreaForHotseat() {
|
||||
Rect keepClearArea;
|
||||
if (!ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
|
||||
// make the height equal to hotseatBarSizePx only
|
||||
keepClearArea = new Rect(0, 0, 0, mDp.hotseatBarSizePx);
|
||||
return keepClearArea;
|
||||
}
|
||||
// the keep clear area in global screen coordinates, in pixels
|
||||
if (mDp.isPhone) {
|
||||
if (mDp.isSeascape()) {
|
||||
@@ -1982,7 +2008,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
mActivityInitListener.unregister();
|
||||
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
|
||||
mActivityRestartListener);
|
||||
mTaskSnapshot = null;
|
||||
mTaskSnapshotCache.clear();
|
||||
}
|
||||
|
||||
private void invalidateHandler() {
|
||||
@@ -2000,7 +2026,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
mActivityInitListener.unregister();
|
||||
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
|
||||
mActivityRestartListener);
|
||||
mTaskSnapshot = null;
|
||||
mTaskSnapshotCache.clear();
|
||||
}
|
||||
|
||||
private void invalidateHandlerWithLauncher() {
|
||||
@@ -2058,38 +2084,40 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
// If there are no targets, then we don't need to capture anything
|
||||
mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
|
||||
} else {
|
||||
final int runningTaskId = mGestureState.getRunningTaskId();
|
||||
boolean finishTransitionPosted = false;
|
||||
// If we already have cached screenshot(s) from running tasks, skip update
|
||||
boolean shouldUpdate = false;
|
||||
int[] runningTaskIds = mGestureState.getRunningTaskIds(mIsSwipeForSplit);
|
||||
for (int id : runningTaskIds) {
|
||||
if (!mTaskSnapshotCache.containsKey(id)) {
|
||||
shouldUpdate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mRecentsAnimationController != null) {
|
||||
// Update the screenshot of the task
|
||||
if (mTaskSnapshot == null) {
|
||||
if (shouldUpdate) {
|
||||
UI_HELPER_EXECUTOR.execute(() -> {
|
||||
if (mRecentsAnimationController == null)
|
||||
return;
|
||||
final ThumbnailData taskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId);
|
||||
// If split case, we should update all split tasks snapshot
|
||||
if (mIsSwipeForSplit) {
|
||||
int[] splitTaskIds = TopTaskTracker.INSTANCE.get(
|
||||
mContext).getRunningSplitTaskIds();
|
||||
for (int i = 0; i < splitTaskIds.length; i++) {
|
||||
// Skip running one because done above.
|
||||
if (splitTaskIds[i] == runningTaskId)
|
||||
continue;
|
||||
|
||||
mRecentsAnimationController.screenshotTask(splitTaskIds[i]);
|
||||
}
|
||||
for (int id : runningTaskIds) {
|
||||
mTaskSnapshotCache.put(
|
||||
id, mRecentsAnimationController.screenshotTask(id));
|
||||
}
|
||||
|
||||
MAIN_EXECUTOR.execute(() -> {
|
||||
mTaskSnapshot = taskSnapshot;
|
||||
if (!updateThumbnail(runningTaskId, false /* refreshView */)) {
|
||||
if (!updateThumbnail(false /* refreshView */)) {
|
||||
setScreenshotCapturedState();
|
||||
}
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
finishTransitionPosted = updateThumbnail(runningTaskId, false /* refreshView */);
|
||||
|
||||
finishTransitionPosted = updateThumbnail(false /* refreshView */);
|
||||
}
|
||||
|
||||
if (!finishTransitionPosted) {
|
||||
setScreenshotCapturedState();
|
||||
}
|
||||
@@ -2097,9 +2125,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
}
|
||||
|
||||
// Returns whether finish transition was posted.
|
||||
private boolean updateThumbnail(int runningTaskId, boolean refreshView) {
|
||||
boolean finishTransitionPosted = false;
|
||||
final TaskView taskView;
|
||||
private boolean updateThumbnail(boolean refreshView) {
|
||||
if (mGestureState.getEndTarget() == HOME
|
||||
|| mGestureState.getEndTarget() == NEW_TASK
|
||||
|| mGestureState.getEndTarget() == ALL_APPS
|
||||
@@ -2108,26 +2134,27 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
// quickswitching to
|
||||
// ensure it's taken in the correct orientation, but no need to update the
|
||||
// thumbnail.
|
||||
taskView = null;
|
||||
} else {
|
||||
taskView = mRecentsView.updateThumbnail(runningTaskId, mTaskSnapshot, refreshView);
|
||||
return false;
|
||||
}
|
||||
if (taskView != null && refreshView && !mCanceled) {
|
||||
|
||||
boolean finishTransitionPosted = false;
|
||||
TaskView updatedTaskView = mRecentsView.updateThumbnail(mTaskSnapshotCache, refreshView);
|
||||
if (updatedTaskView != null && refreshView && !mCanceled) {
|
||||
// Defer finishing the animation until the next launcher frame with the
|
||||
// new thumbnail
|
||||
finishTransitionPosted = ViewUtils.postFrameDrawn(taskView,
|
||||
finishTransitionPosted = ViewUtils.postFrameDrawn(updatedTaskView,
|
||||
() -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
|
||||
this::isCanceled);
|
||||
}
|
||||
|
||||
return finishTransitionPosted;
|
||||
}
|
||||
|
||||
private void setScreenshotCapturedState() {
|
||||
// If we haven't posted a draw callback, set the state immediately.
|
||||
Object traceToken = TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT,
|
||||
TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS);
|
||||
TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT);
|
||||
mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
|
||||
TraceHelper.INSTANCE.endSection(traceToken);
|
||||
TraceHelper.INSTANCE.endSection();
|
||||
}
|
||||
|
||||
private void finishCurrentTransitionToRecents() {
|
||||
@@ -2269,16 +2296,27 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
if (!mCanceled) {
|
||||
TaskView nextTask = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
|
||||
if (nextTask != null) {
|
||||
Task.TaskKey nextTaskKey = nextTask.getTask().key;
|
||||
int taskId = nextTaskKey.id;
|
||||
mGestureState.updateLastStartedTaskId(taskId);
|
||||
boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
|
||||
.contains(taskId);
|
||||
int[] taskIds = nextTask.getTaskIds();
|
||||
StringBuilder nextTaskLog = new StringBuilder();
|
||||
for (TaskIdAttributeContainer c : nextTask.getTaskIdAttributeContainers()) {
|
||||
if (c == null) {
|
||||
continue;
|
||||
}
|
||||
nextTaskLog
|
||||
.append("[id: ")
|
||||
.append(c.getTask().key.id)
|
||||
.append(", pkg: ")
|
||||
.append(c.getTask().key.getPackageName())
|
||||
.append("] | ");
|
||||
}
|
||||
mGestureState.updateLastStartedTaskIds(taskIds);
|
||||
boolean hasTaskPreviouslyAppeared = Arrays.stream(taskIds).anyMatch(
|
||||
taskId -> mGestureState.getPreviouslyAppearedTaskIds()
|
||||
.contains(taskId));
|
||||
if (!hasTaskPreviouslyAppeared) {
|
||||
ActiveGestureLog.INSTANCE.trackEvent(EXPECTING_TASK_APPEARED);
|
||||
}
|
||||
ActiveGestureLog.INSTANCE.addLog("Launching task: id=" + taskId
|
||||
+ " pkg=" + nextTaskKey.getPackageName());
|
||||
ActiveGestureLog.INSTANCE.addLog("Launching task: " + nextTaskLog);
|
||||
nextTask.launchTask(success -> {
|
||||
resultCallback.accept(success);
|
||||
if (success) {
|
||||
@@ -2353,7 +2391,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
|
||||
if (mRecentsAnimationController != null) {
|
||||
boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTargets).anyMatch(
|
||||
targetCompat -> targetCompat.taskId == mGestureState.getLastStartedTaskId());
|
||||
mGestureState.mLastStartedTaskIdPredicate);
|
||||
if (!mStateCallback.hasStates(STATE_GESTURE_COMPLETED) && !hasStartedTaskBefore) {
|
||||
// This is a special case, if a task is started mid-gesture that wasn't a part
|
||||
// of a
|
||||
@@ -2366,7 +2404,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
|
||||
} else if (handleTaskAppeared(appearedTaskTargets)) {
|
||||
Optional<RemoteAnimationTarget> taskTargetOptional = Arrays.stream(appearedTaskTargets)
|
||||
.filter(targetCompat -> targetCompat.taskId == mGestureState.getLastStartedTaskId())
|
||||
.filter(mGestureState.mLastStartedTaskIdPredicate)
|
||||
.findFirst();
|
||||
if (!taskTargetOptional.isPresent()) {
|
||||
ActiveGestureLog.INSTANCE.addLog("No appeared task matching started task id");
|
||||
@@ -2437,11 +2475,16 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
* resume if we finish the controller.
|
||||
*/
|
||||
protected int getLastAppearedTaskIndex() {
|
||||
return mRecentsView == null
|
||||
? -1
|
||||
: mGestureState.getLastAppearedTaskId() != -1
|
||||
? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
|
||||
: mRecentsView.getRunningTaskIndex();
|
||||
if (mRecentsView == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
OptionalInt firstValidTaskId = Arrays.stream(mGestureState.getLastAppearedTaskIds())
|
||||
.filter(i -> i != -1)
|
||||
.findFirst();
|
||||
return firstValidTaskId.isPresent()
|
||||
? mRecentsView.getTaskIndexForId(firstValidTaskId.getAsInt())
|
||||
: mRecentsView.getRunningTaskIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2450,7 +2493,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
* but before that task appeared.
|
||||
*/
|
||||
protected boolean hasStartedNewTask() {
|
||||
return mGestureState.getLastStartedTaskId() != -1;
|
||||
return mGestureState.getLastStartedTaskIds()[0] != -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2493,7 +2536,6 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
taskViewSimulator.apply(remoteHandle.getTransformParams());
|
||||
}
|
||||
}
|
||||
ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
|
||||
}
|
||||
|
||||
// Scaling of RecentsView during quick switch based on amount of recents scroll
|
||||
@@ -2514,11 +2556,11 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
|
||||
if (scrollOffset < mQuickSwitchScaleScrollThreshold) {
|
||||
scaleProgress = Utilities.mapToRange(scrollOffset, 0, mQuickSwitchScaleScrollThreshold,
|
||||
0, maxScaleProgress, ACCEL_DEACCEL);
|
||||
0, maxScaleProgress, ACCELERATE_DECELERATE);
|
||||
} else if (scrollOffset > (maxScrollOffset - mQuickSwitchScaleScrollThreshold)) {
|
||||
scaleProgress = Utilities.mapToRange(scrollOffset,
|
||||
(maxScrollOffset - mQuickSwitchScaleScrollThreshold), maxScrollOffset,
|
||||
maxScaleProgress, 0, ACCEL_DEACCEL);
|
||||
maxScaleProgress, 0, ACCELERATE_DECELERATE);
|
||||
}
|
||||
|
||||
return scaleProgress;
|
||||
@@ -2549,7 +2591,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
// "Catch up" with the displacement at mTaskbarCatchUpThreshold.
|
||||
if (displacement < mTaskbarCatchUpThreshold) {
|
||||
return Utilities.mapToRange(displacement, mTaskbarAppWindowThreshold,
|
||||
mTaskbarCatchUpThreshold, 0, mTaskbarCatchUpThreshold, ACCEL_DEACCEL);
|
||||
mTaskbarCatchUpThreshold, 0, mTaskbarCatchUpThreshold, ACCELERATE_DECELERATE);
|
||||
}
|
||||
|
||||
return displacement;
|
||||
@@ -2564,27 +2606,6 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>, Q extends
|
||||
mRecentsAnimationTargets.nonApps, shown, null /* animatorHandler */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for winscope tracing, see launcher_trace.proto
|
||||
*
|
||||
* @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
|
||||
* @param inputConsumerProto The parent of this proto message.
|
||||
*/
|
||||
public void writeToProto(InputConsumerProto.Builder inputConsumerProto) {
|
||||
SwipeHandlerProto.Builder swipeHandlerProto = SwipeHandlerProto.newBuilder();
|
||||
|
||||
mGestureState.writeToProto(swipeHandlerProto);
|
||||
|
||||
swipeHandlerProto.setIsRecentsAttachedToAppWindow(
|
||||
mAnimationFactory.isRecentsAttachedToAppWindow());
|
||||
swipeHandlerProto.setScrollOffset(mRecentsView == null
|
||||
? 0
|
||||
: mRecentsView.getScrollOffset());
|
||||
swipeHandlerProto.setAppToOverviewProgress(mCurrentShift.value);
|
||||
|
||||
inputConsumerProto.setSwipeHandler(swipeHandlerProto);
|
||||
}
|
||||
|
||||
public interface Factory {
|
||||
AbsSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs);
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import static com.android.app.animation.Interpolators.ACCELERATE_2;
|
||||
import static com.android.app.animation.Interpolators.INSTANT;
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
|
||||
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
|
||||
import static com.android.launcher3.anim.Interpolators.INSTANT;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION;
|
||||
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
|
||||
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
|
||||
@@ -75,11 +75,11 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
|
||||
* Utility class which abstracts out the logical differences between Launcher
|
||||
* and RecentsActivity.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_TYPE>,
|
||||
ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE>> {
|
||||
public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_TYPE>, ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE>> {
|
||||
|
||||
public final boolean rotationSupportedByActivity;
|
||||
|
||||
@@ -87,7 +87,8 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
|
||||
private STATE_TYPE mTargetState;
|
||||
|
||||
@Nullable private Runnable mOnInitBackgroundStateUICallback = null;
|
||||
@Nullable
|
||||
private Runnable mOnInitBackgroundStateUICallback = null;
|
||||
|
||||
protected BaseActivityInterface(boolean rotationSupportedByActivity,
|
||||
STATE_TYPE overviewState, STATE_TYPE backgroundState) {
|
||||
@@ -98,8 +99,11 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
|
||||
/**
|
||||
* Called when the current gesture transition is cancelled.
|
||||
* @param activityVisible Whether the user can see the changes we make here, so try to animate.
|
||||
* @param endTarget If the gesture ended before we got cancelled, where we were headed.
|
||||
*
|
||||
* @param activityVisible Whether the user can see the changes we make here, so
|
||||
* try to animate.
|
||||
* @param endTarget If the gesture ended before we got cancelled, where we
|
||||
* were headed.
|
||||
*/
|
||||
public void onTransitionCancelled(boolean activityVisible,
|
||||
@Nullable GestureState.GestureEndTarget endTarget) {
|
||||
@@ -134,7 +138,8 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
PagedOrientationHandler orientationHandler);
|
||||
|
||||
/** Called when the animation to home has fully settled. */
|
||||
public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) {}
|
||||
public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) {
|
||||
}
|
||||
|
||||
public abstract void onAssistantVisibilityChanged(float visibility);
|
||||
|
||||
@@ -145,9 +150,11 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
Predicate<Boolean> onInitListener);
|
||||
|
||||
/**
|
||||
* Sets a callback to be run when an activity launch happens while launcher is not yet resumed.
|
||||
* Sets a callback to be run when an activity launch happens while launcher is
|
||||
* not yet resumed.
|
||||
*/
|
||||
public void setOnDeferredActivityLaunchCallback(Runnable r) {}
|
||||
public void setOnDeferredActivityLaunchCallback(Runnable r) {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public abstract ACTIVITY_TYPE getCreatedActivity();
|
||||
@@ -330,7 +337,8 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the overview grid non-focused task size for the provided device configuration.
|
||||
* Calculates the overview grid non-focused task size for the provided device
|
||||
* configuration.
|
||||
*/
|
||||
public final void calculateGridTaskSize(Context context, DeviceProfile dp, Rect outRect,
|
||||
PagedOrientationHandler orientedState) {
|
||||
@@ -375,8 +383,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
int minimumHorizontalPadding = 0;
|
||||
if (!isGridOnlyOverview) {
|
||||
float maxScale = context.getResources().getFloat(R.dimen.overview_modal_max_scale);
|
||||
minimumHorizontalPadding =
|
||||
Math.round((dp.availableWidthPx - outRect.width() * maxScale) / 2);
|
||||
minimumHorizontalPadding = Math.round((dp.availableWidthPx - outRect.width() * maxScale) / 2);
|
||||
}
|
||||
calculateTaskSizeInternal(
|
||||
context,
|
||||
@@ -384,13 +391,14 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
dp.overviewTaskMarginPx,
|
||||
claimedSpaceBelow,
|
||||
minimumHorizontalPadding,
|
||||
1f /*maxScale*/,
|
||||
1f /* maxScale */,
|
||||
Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM,
|
||||
outRect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the gesture ends and the animation starts towards the given target. Used to add
|
||||
* Called when the gesture ends and the animation starts towards the given
|
||||
* target. Used to add
|
||||
* an optional additional animation with the same duration.
|
||||
*/
|
||||
public @Nullable Animator getParallelAnimationToLauncher(
|
||||
@@ -423,9 +431,13 @@ 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.)
|
||||
* 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();
|
||||
@@ -456,11 +468,13 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
void createActivityInterface(long transitionLength);
|
||||
|
||||
/**
|
||||
* @param attached Whether to show RecentsView alongside the app window. If false, recents
|
||||
* @param attached Whether to show RecentsView alongside the app window. If
|
||||
* false, recents
|
||||
* will be hidden by some property we can animate, e.g. alpha.
|
||||
* @param animate Whether to animate recents to/from its new attached state.
|
||||
* @param animate Whether to animate recents to/from its new attached state.
|
||||
*/
|
||||
default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { }
|
||||
default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) {
|
||||
}
|
||||
|
||||
default boolean isRecentsAttachedToAppWindow() {
|
||||
return false;
|
||||
@@ -471,7 +485,8 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
}
|
||||
|
||||
/** Called when the gesture ends and we know what state it is going towards */
|
||||
default void setEndTarget(GestureState.GestureEndTarget endTarget) { }
|
||||
default void setEndTarget(GestureState.GestureEndTarget endTarget) {
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultAnimationFactory implements AnimationFactory {
|
||||
@@ -508,21 +523,24 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
AnimatorPlaybackController controller = pa.createPlaybackController();
|
||||
mActivity.getStateManager().setCurrentUserControlledAnimation(controller);
|
||||
|
||||
// Since we are changing the start position of the UI, reapply the state, at the end
|
||||
// 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 ? mTargetState : mBackgroundState,
|
||||
false));
|
||||
|
||||
RecentsView recentsView = mActivity.getOverviewPanel();
|
||||
AnimatorControllerWithResistance controllerWithResistance =
|
||||
AnimatorControllerWithResistance.createForRecents(controller, mActivity,
|
||||
AnimatorControllerWithResistance controllerWithResistance = AnimatorControllerWithResistance
|
||||
.createForRecents(controller, mActivity,
|
||||
recentsView.getPagedViewOrientedState(), mActivity.getDeviceProfile(),
|
||||
recentsView, RECENTS_SCALE_PROPERTY, recentsView,
|
||||
TASK_SECONDARY_TRANSLATION);
|
||||
mCallback.accept(controllerWithResistance);
|
||||
|
||||
// Creating the activity controller animation sometimes reapplies the launcher state
|
||||
// (because we set the animation as the current state animation), so we reapply the
|
||||
// Creating the activity controller animation sometimes reapplies the launcher
|
||||
// state
|
||||
// (because we set the animation as the current state animation), so we reapply
|
||||
// the
|
||||
// attached state here as well to ensure recents is shown/hidden appropriately.
|
||||
if (DisplayController.getNavigationMode(mActivity) == NavigationMode.NO_BUTTON) {
|
||||
setRecentsAttachedToAppWindow(mIsAttachedToWindow, false);
|
||||
@@ -548,12 +566,13 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
if (attached) {
|
||||
mHasEverAttachedToWindow = true;
|
||||
}
|
||||
}});
|
||||
}
|
||||
});
|
||||
|
||||
long animationDuration = animate ? RECENTS_ATTACH_DURATION : 0;
|
||||
Animator fadeAnim = mActivity.getStateManager()
|
||||
.createStateElementAnimation(INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
|
||||
fadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2);
|
||||
fadeAnim.setInterpolator(attached ? INSTANT : ACCELERATE_2);
|
||||
fadeAnim.setDuration(animationDuration);
|
||||
animatorSet.play(fadeAnim);
|
||||
|
||||
@@ -584,7 +603,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
}
|
||||
|
||||
protected void createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa) {
|
||||
// Scale down recents from being full screen to being in overview.
|
||||
// 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);
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.quickstep;
|
||||
|
||||
import static android.os.IBinder.FLAG_ONEWAY;
|
||||
|
||||
import android.os.Binder;
|
||||
import android.os.Binder.ProxyTransactListener;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.os.Trace;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.util.TraceHelper;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import kotlin.random.Random;
|
||||
|
||||
/**
|
||||
* A binder proxy transaction listener for tracking binder calls on main thread.
|
||||
*/
|
||||
public class BinderTracker {
|
||||
|
||||
private static final String TAG = "BinderTracker";
|
||||
|
||||
// Common IPCs that are ok to block the main thread.
|
||||
private static final Set<String> sAllowedFrameworkClasses = Set.of(
|
||||
"android.view.IWindowSession",
|
||||
"android.os.IPowerManager",
|
||||
"android.os.IServiceManager");
|
||||
|
||||
/**
|
||||
* Starts tracking binder class and returns a {@link SafeCloseable} to end tracking
|
||||
*/
|
||||
public static SafeCloseable startTracking(Consumer<BinderCallSite> callback) {
|
||||
TraceHelper current = TraceHelper.INSTANCE;
|
||||
|
||||
TraceHelperExtension helper = new TraceHelperExtension(callback);
|
||||
TraceHelper.INSTANCE = helper;
|
||||
Binder.setProxyTransactListener(helper);
|
||||
|
||||
return () -> {
|
||||
Binder.setProxyTransactListener(null);
|
||||
TraceHelper.INSTANCE = current;
|
||||
};
|
||||
}
|
||||
|
||||
private static final LinkedList<String> mMainThreadTraceStack = new LinkedList<>();
|
||||
private static final LinkedList<String> mMainThreadIgnoreIpcStack = new LinkedList<>();
|
||||
|
||||
private static class TraceHelperExtension extends TraceHelper implements ProxyTransactListener {
|
||||
|
||||
private final Consumer<BinderCallSite> mUnexpectedTransactionCallback;
|
||||
|
||||
TraceHelperExtension(Consumer<BinderCallSite> unexpectedTransactionCallback) {
|
||||
mUnexpectedTransactionCallback = unexpectedTransactionCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginSection(String sectionName) {
|
||||
if (isMainThread()) {
|
||||
mMainThreadTraceStack.add(sectionName);
|
||||
}
|
||||
super.beginSection(sectionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SafeCloseable beginAsyncSection(String sectionName) {
|
||||
if (!isMainThread()) {
|
||||
return super.beginAsyncSection(sectionName);
|
||||
}
|
||||
|
||||
mMainThreadTraceStack.add(sectionName);
|
||||
int cookie = Random.Default.nextInt();
|
||||
Trace.beginAsyncSection(sectionName, cookie);
|
||||
return () -> {
|
||||
Trace.endAsyncSection(sectionName, cookie);
|
||||
mMainThreadTraceStack.remove(sectionName);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endSection() {
|
||||
super.endSection();
|
||||
if (isMainThread()) {
|
||||
mMainThreadTraceStack.pollLast();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SafeCloseable allowIpcs(String rpcName) {
|
||||
if (!isMainThread()) {
|
||||
return super.allowIpcs(rpcName);
|
||||
}
|
||||
|
||||
mMainThreadTraceStack.add(rpcName);
|
||||
mMainThreadIgnoreIpcStack.add(rpcName);
|
||||
int cookie = Random.Default.nextInt();
|
||||
Trace.beginAsyncSection(rpcName, cookie);
|
||||
return () -> {
|
||||
Trace.endAsyncSection(rpcName, cookie);
|
||||
mMainThreadTraceStack.remove(rpcName);
|
||||
mMainThreadIgnoreIpcStack.remove(rpcName);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onTransactStarted(IBinder binder, int transactionCode, int flags) {
|
||||
if (!isMainThread() || (flags & FLAG_ONEWAY) == FLAG_ONEWAY) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String ipcBypass = mMainThreadIgnoreIpcStack.peekLast();
|
||||
String descriptor;
|
||||
try {
|
||||
descriptor = binder.getInterfaceDescriptor();
|
||||
if (sAllowedFrameworkClasses.contains(descriptor)) {
|
||||
return null;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error getting IPC descriptor", e);
|
||||
descriptor = binder.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
if (ipcBypass == null) {
|
||||
mUnexpectedTransactionCallback.accept(new BinderCallSite(
|
||||
mMainThreadTraceStack.peekLast(), descriptor, transactionCode));
|
||||
} else {
|
||||
Log.d(TAG, "MainThread-IPC " + descriptor + " ignored due to " + ipcBypass);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onTransactStarted(IBinder binder, int transactionCode) {
|
||||
// Do nothing
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransactEnded(Object session) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isMainThread() {
|
||||
return Thread.currentThread() == Looper.getMainLooper().getThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about a binder call
|
||||
*/
|
||||
public static class BinderCallSite {
|
||||
|
||||
@Nullable
|
||||
public final String activeTrace;
|
||||
public final String descriptor;
|
||||
public final int transactionCode;
|
||||
|
||||
BinderCallSite(String activeTrace, String descriptor, int transactionCode) {
|
||||
this.activeTrace = activeTrace;
|
||||
this.descriptor = descriptor;
|
||||
this.transactionCode = transactionCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,13 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
|
||||
import static android.content.Intent.EXTRA_COMPONENT_NAME;
|
||||
import static android.content.Intent.EXTRA_USER;
|
||||
|
||||
import static com.android.app.animation.Interpolators.ACCELERATE;
|
||||
import static com.android.launcher3.GestureNavContract.EXTRA_GESTURE_CONTRACT;
|
||||
import static com.android.launcher3.GestureNavContract.EXTRA_ICON_POSITION;
|
||||
import static com.android.launcher3.GestureNavContract.EXTRA_ICON_SURFACE;
|
||||
import static com.android.launcher3.GestureNavContract.EXTRA_ON_FINISH_CALLBACK;
|
||||
import static com.android.launcher3.GestureNavContract.EXTRA_REMOTE_CALLBACK;
|
||||
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL;
|
||||
import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
@@ -296,7 +296,7 @@ public class FallbackSwipeHandler extends
|
||||
@Override
|
||||
public AnimatorPlaybackController createActivityAnimationToHome() {
|
||||
PendingAnimation pa = new PendingAnimation(mDuration);
|
||||
pa.setFloat(mRecentsAlpha, AnimatedFloat.VALUE, 0, ACCEL);
|
||||
pa.setFloat(mRecentsAlpha, AnimatedFloat.VALUE, 0, ACCELERATE);
|
||||
return pa.createPlaybackController();
|
||||
}
|
||||
|
||||
@@ -324,7 +324,7 @@ public class FallbackSwipeHandler extends
|
||||
@Override
|
||||
public void playAtomicAnimation(float velocity) {
|
||||
ObjectAnimator alphaAnim = mHomeAlpha.animateToValue(mHomeAlpha.value, 1);
|
||||
alphaAnim.setDuration(mDuration).setInterpolator(ACCEL);
|
||||
alphaAnim.setDuration(mDuration).setInterpolator(ACCELERATE);
|
||||
alphaAnim.start();
|
||||
|
||||
if (mRunningOverHome) {
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
|
||||
|
||||
import static com.android.launcher3.MotionEventsUtils.isTrackpadFourFingerSwipe;
|
||||
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
|
||||
import static com.android.launcher3.MotionEventsUtils.isTrackpadThreeFingerSwipe;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
|
||||
@@ -37,8 +38,6 @@ import android.view.RemoteAnimationTarget;
|
||||
|
||||
import com.android.launcher3.statemanager.BaseState;
|
||||
import com.android.launcher3.statemanager.StatefulActivity;
|
||||
import com.android.launcher3.tracing.GestureStateProto;
|
||||
import com.android.launcher3.tracing.SwipeHandlerProto;
|
||||
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
|
||||
import com.android.quickstep.util.ActiveGestureErrorDetector;
|
||||
import com.android.quickstep.util.ActiveGestureLog;
|
||||
@@ -46,51 +45,68 @@ import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Manages the state for an active system gesture, listens for events from the system and Launcher,
|
||||
* Manages the state for an active system gesture, listens for events from the
|
||||
* system and Launcher,
|
||||
* and fires events when the states change.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.R)
|
||||
public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationListener {
|
||||
|
||||
final Predicate<RemoteAnimationTarget> mLastStartedTaskIdPredicate = new Predicate<>() {
|
||||
@Override
|
||||
public boolean test(RemoteAnimationTarget targetCompat) {
|
||||
for (int taskId : mLastStartedTaskId) {
|
||||
if (targetCompat.taskId == taskId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines the end targets of a gesture and the associated state.
|
||||
*/
|
||||
public enum GestureEndTarget {
|
||||
HOME(true, LAUNCHER_STATE_HOME, false, GestureStateProto.GestureEndTarget.HOME),
|
||||
HOME(true, LAUNCHER_STATE_HOME, false),
|
||||
|
||||
RECENTS(true, LAUNCHER_STATE_OVERVIEW, true, GestureStateProto.GestureEndTarget.RECENTS),
|
||||
RECENTS(true, LAUNCHER_STATE_OVERVIEW, true),
|
||||
|
||||
NEW_TASK(false, LAUNCHER_STATE_BACKGROUND, true,
|
||||
GestureStateProto.GestureEndTarget.NEW_TASK),
|
||||
NEW_TASK(false, LAUNCHER_STATE_BACKGROUND, true),
|
||||
|
||||
LAST_TASK(false, LAUNCHER_STATE_BACKGROUND, true,
|
||||
GestureStateProto.GestureEndTarget.LAST_TASK),
|
||||
LAST_TASK(false, LAUNCHER_STATE_BACKGROUND, true),
|
||||
|
||||
ALL_APPS(true, LAUNCHER_STATE_ALLAPPS, false, GestureStateProto.GestureEndTarget.ALL_APPS);
|
||||
ALL_APPS(true, LAUNCHER_STATE_ALLAPPS, false);
|
||||
|
||||
GestureEndTarget(boolean isLauncher, int containerType, boolean recentsAttachedToAppWindow,
|
||||
GestureStateProto.GestureEndTarget protoEndTarget) {
|
||||
GestureEndTarget(boolean isLauncher, int containerType,
|
||||
boolean recentsAttachedToAppWindow) {
|
||||
this.isLauncher = isLauncher;
|
||||
this.containerType = containerType;
|
||||
this.recentsAttachedToAppWindow = recentsAttachedToAppWindow;
|
||||
this.protoEndTarget = protoEndTarget;
|
||||
}
|
||||
|
||||
/** Whether the target is in the launcher activity. Implicitly, if the end target is going
|
||||
to Launcher, then we can not interrupt the animation to start another gesture. */
|
||||
/**
|
||||
* Whether the target is in the launcher activity. Implicitly, if the end target
|
||||
* is going
|
||||
* to Launcher, then we can not interrupt the animation to start another
|
||||
* gesture.
|
||||
*/
|
||||
public final boolean isLauncher;
|
||||
/** Used to log where the user ended up after the gesture ends */
|
||||
public final int containerType;
|
||||
/** Whether RecentsView should be attached to the window as we animate to this target */
|
||||
/**
|
||||
* Whether RecentsView should be attached to the window as we animate to this
|
||||
* target
|
||||
*/
|
||||
public final boolean recentsAttachedToAppWindow;
|
||||
/** The GestureStateProto enum value, used for winscope tracing. See launcher_trace.proto */
|
||||
public final GestureStateProto.GestureEndTarget protoEndTarget;
|
||||
}
|
||||
|
||||
private static final String TAG = "GestureState";
|
||||
@@ -99,6 +115,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
public static final GestureState DEFAULT_STATE = new GestureState();
|
||||
|
||||
private static int FLAG_COUNT = 0;
|
||||
|
||||
private static int getNextStateFlag(String name) {
|
||||
if (DEBUG_STATES) {
|
||||
STATE_NAMES.add(name);
|
||||
@@ -109,37 +126,33 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
}
|
||||
|
||||
// Called when the end target as been set
|
||||
public static final int STATE_END_TARGET_SET =
|
||||
getNextStateFlag("STATE_END_TARGET_SET");
|
||||
public static final int STATE_END_TARGET_SET = getNextStateFlag("STATE_END_TARGET_SET");
|
||||
|
||||
// Called when the end target animation has finished
|
||||
public static final int STATE_END_TARGET_ANIMATION_FINISHED =
|
||||
getNextStateFlag("STATE_END_TARGET_ANIMATION_FINISHED");
|
||||
public static final int STATE_END_TARGET_ANIMATION_FINISHED = getNextStateFlag(
|
||||
"STATE_END_TARGET_ANIMATION_FINISHED");
|
||||
|
||||
// Called when the recents animation has been requested to start
|
||||
public static final int STATE_RECENTS_ANIMATION_INITIALIZED =
|
||||
getNextStateFlag("STATE_RECENTS_ANIMATION_INITIALIZED");
|
||||
public static final int STATE_RECENTS_ANIMATION_INITIALIZED = getNextStateFlag(
|
||||
"STATE_RECENTS_ANIMATION_INITIALIZED");
|
||||
|
||||
// Called when the recents animation is started and the TaskAnimationManager has been updated
|
||||
// Called when the recents animation is started and the TaskAnimationManager has
|
||||
// been updated
|
||||
// with the controller and targets
|
||||
public static final int STATE_RECENTS_ANIMATION_STARTED =
|
||||
getNextStateFlag("STATE_RECENTS_ANIMATION_STARTED");
|
||||
public static final int STATE_RECENTS_ANIMATION_STARTED = getNextStateFlag("STATE_RECENTS_ANIMATION_STARTED");
|
||||
|
||||
// Called when the recents animation is canceled
|
||||
public static final int STATE_RECENTS_ANIMATION_CANCELED =
|
||||
getNextStateFlag("STATE_RECENTS_ANIMATION_CANCELED");
|
||||
public static final int STATE_RECENTS_ANIMATION_CANCELED = getNextStateFlag("STATE_RECENTS_ANIMATION_CANCELED");
|
||||
|
||||
// Called when the recents animation finishes
|
||||
public static final int STATE_RECENTS_ANIMATION_FINISHED =
|
||||
getNextStateFlag("STATE_RECENTS_ANIMATION_FINISHED");
|
||||
public static final int STATE_RECENTS_ANIMATION_FINISHED = getNextStateFlag("STATE_RECENTS_ANIMATION_FINISHED");
|
||||
|
||||
// Always called when the recents animation ends (regardless of cancel or finish)
|
||||
public static final int STATE_RECENTS_ANIMATION_ENDED =
|
||||
getNextStateFlag("STATE_RECENTS_ANIMATION_ENDED");
|
||||
// Always called when the recents animation ends (regardless of cancel or
|
||||
// finish)
|
||||
public static final int STATE_RECENTS_ANIMATION_ENDED = getNextStateFlag("STATE_RECENTS_ANIMATION_ENDED");
|
||||
|
||||
// Called when RecentsView stops scrolling and settles on a TaskView.
|
||||
public static final int STATE_RECENTS_SCROLLING_FINISHED =
|
||||
getNextStateFlag("STATE_RECENTS_SCROLLING_FINISHED");
|
||||
public static final int STATE_RECENTS_SCROLLING_FINISHED = getNextStateFlag("STATE_RECENTS_SCROLLING_FINISHED");
|
||||
|
||||
// Needed to interact with the current activity
|
||||
private final Intent mHomeIntent;
|
||||
@@ -150,15 +163,10 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
|
||||
public enum TrackpadGestureType {
|
||||
NONE,
|
||||
// Assigned before we know whether it's a 3-finger or 4-finger gesture.
|
||||
MULTI_FINGER,
|
||||
THREE_FINGER,
|
||||
FOUR_FINGER;
|
||||
|
||||
public static TrackpadGestureType getTrackpadGestureType(MotionEvent event) {
|
||||
if (!isTrackpadMultiFingerSwipe(event)) {
|
||||
return TrackpadGestureType.NONE;
|
||||
}
|
||||
if (isTrackpadThreeFingerSwipe(event)) {
|
||||
return TrackpadGestureType.THREE_FINGER;
|
||||
}
|
||||
@@ -166,16 +174,16 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
return TrackpadGestureType.FOUR_FINGER;
|
||||
}
|
||||
|
||||
return TrackpadGestureType.MULTI_FINGER;
|
||||
return TrackpadGestureType.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE;
|
||||
private CachedTaskInfo mRunningTask;
|
||||
private GestureEndTarget mEndTarget;
|
||||
private RemoteAnimationTarget mLastAppearedTaskTarget;
|
||||
private RemoteAnimationTarget[] mLastAppearedTaskTargets;
|
||||
private Set<Integer> mPreviouslyAppearedTaskIds = new HashSet<>();
|
||||
private int mLastStartedTaskId = -1;
|
||||
private int[] mLastStartedTaskId = new int[] { INVALID_TASK_ID, INVALID_TASK_ID };
|
||||
private RecentsAnimationController mRecentsAnimationController;
|
||||
private HashMap<Integer, ThumbnailData> mRecentsAnimationCanceledSnapshots;
|
||||
|
||||
@@ -201,7 +209,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
mGestureId = other.mGestureId;
|
||||
mRunningTask = other.mRunningTask;
|
||||
mEndTarget = other.mEndTarget;
|
||||
mLastAppearedTaskTarget = other.mLastAppearedTaskTarget;
|
||||
mLastAppearedTaskTargets = other.mLastAppearedTaskTargets;
|
||||
mPreviouslyAppearedTaskIds = other.mPreviouslyAppearedTaskIds;
|
||||
mLastStartedTaskId = other.mLastStartedTaskId;
|
||||
}
|
||||
@@ -229,7 +237,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the gesture state has the provided {@param stateMask} flags set.
|
||||
* @return whether the gesture state has the provided {@param stateMask} flags
|
||||
* set.
|
||||
*/
|
||||
public boolean hasState(int stateMask) {
|
||||
return mStateCallback.hasStates(stateMask);
|
||||
@@ -243,7 +252,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a callback for when the states matching the given {@param stateMask} is set.
|
||||
* Adds a callback for when the states matching the given {@param stateMask} is
|
||||
* set.
|
||||
*/
|
||||
public void runOnceAtState(int stateMask, Runnable callback) {
|
||||
mStateCallback.runOnceAtState(stateMask, callback);
|
||||
@@ -264,10 +274,10 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the interface to the activity handing the UI updates for this gesture.
|
||||
* @return the interface to the activity handing the UI updates for this
|
||||
* gesture.
|
||||
*/
|
||||
public <S extends BaseState<S>,
|
||||
T extends StatefulActivity<S>> BaseActivityInterface<S, T> getActivityInterface() {
|
||||
public <S extends BaseState<S>, T extends StatefulActivity<S>> BaseActivityInterface<S, T> getActivityInterface() {
|
||||
return mActivityInterface;
|
||||
}
|
||||
|
||||
@@ -279,7 +289,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the gesture is is from the trackpad, if so, whether 3-finger, or 4-finger
|
||||
* Sets if the gesture is is from the trackpad, if so, whether 3-finger, or
|
||||
* 4-finger
|
||||
*/
|
||||
public void setTrackpadGestureType(TrackpadGestureType trackpadGestureType) {
|
||||
mTrackpadGestureType = trackpadGestureType;
|
||||
@@ -305,14 +316,35 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the running task id for this gesture.
|
||||
* @param getMultipleTasks Whether multiple tasks or not are to be returned (for
|
||||
* split)
|
||||
* @return the running task ids for this gesture.
|
||||
*/
|
||||
public int getRunningTaskId() {
|
||||
return mRunningTask != null ? mRunningTask.getTaskId() : -1;
|
||||
public int[] getRunningTaskIds(boolean getMultipleTasks) {
|
||||
if (mRunningTask == null) {
|
||||
return new int[] { INVALID_TASK_ID, INVALID_TASK_ID };
|
||||
} else {
|
||||
int cachedTasksSize = mRunningTask.mAllCachedTasks.size();
|
||||
int count = Math.min(cachedTasksSize, getMultipleTasks ? 2 : 1);
|
||||
int[] runningTaskIds = new int[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
runningTaskIds[i] = mRunningTask.mAllCachedTasks.get(i).taskId;
|
||||
}
|
||||
return runningTaskIds;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the running task for the gesture to be the given {@param runningTask}.
|
||||
* @see #getRunningTaskIds(boolean)
|
||||
* @return the single top-most running taskId for this gesture
|
||||
*/
|
||||
public int getTopRunningTaskId() {
|
||||
return getRunningTaskIds(false /* getMultipleTasks */)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the running task for the gesture to be the given
|
||||
* {@param runningTask}.
|
||||
*/
|
||||
public void updateRunningTask(CachedTaskInfo runningTask) {
|
||||
mRunningTask = runningTask;
|
||||
@@ -321,18 +353,26 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
/**
|
||||
* Updates the last task that appeared during this gesture.
|
||||
*/
|
||||
public void updateLastAppearedTaskTarget(RemoteAnimationTarget lastAppearedTaskTarget) {
|
||||
mLastAppearedTaskTarget = lastAppearedTaskTarget;
|
||||
if (lastAppearedTaskTarget != null) {
|
||||
mPreviouslyAppearedTaskIds.add(lastAppearedTaskTarget.taskId);
|
||||
public void updateLastAppearedTaskTargets(RemoteAnimationTarget[] lastAppearedTaskTargets) {
|
||||
mLastAppearedTaskTargets = lastAppearedTaskTargets;
|
||||
for (RemoteAnimationTarget target : lastAppearedTaskTargets) {
|
||||
if (target == null) {
|
||||
continue;
|
||||
}
|
||||
mPreviouslyAppearedTaskIds.add(target.taskId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The id of the task that appeared during this gesture.
|
||||
*/
|
||||
public int getLastAppearedTaskId() {
|
||||
return mLastAppearedTaskTarget != null ? mLastAppearedTaskTarget.taskId : -1;
|
||||
public int[] getLastAppearedTaskIds() {
|
||||
if (mLastAppearedTaskTargets == null) {
|
||||
return new int[] { INVALID_TASK_ID, INVALID_TASK_ID };
|
||||
} else {
|
||||
return Arrays.stream(mLastAppearedTaskTargets)
|
||||
.mapToInt(target -> target != null ? target.taskId : INVALID_TASK_ID).toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public void updatePreviouslyAppearedTaskIds(Set<Integer> previouslyAppearedTaskIds) {
|
||||
@@ -344,17 +384,19 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last task that we started via startActivityFromRecents() during this gesture.
|
||||
* Updates the last task that we started via startActivityFromRecents() during
|
||||
* this gesture.
|
||||
*/
|
||||
public void updateLastStartedTaskId(int lastStartedTaskId) {
|
||||
public void updateLastStartedTaskIds(int[] lastStartedTaskId) {
|
||||
mLastStartedTaskId = lastStartedTaskId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The id of the task that was most recently started during this gesture, or -1 if
|
||||
* no task has been started yet (i.e. we haven't settled on a new task).
|
||||
* @return The id of the task that was most recently started during this
|
||||
* gesture, or -1 if
|
||||
* no task has been started yet (i.e. we haven't settled on a new task).
|
||||
*/
|
||||
public int getLastStartedTaskId() {
|
||||
public int[] getLastStartedTaskIds() {
|
||||
return mLastStartedTaskId;
|
||||
}
|
||||
|
||||
@@ -366,15 +408,18 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the end target of this gesture and immediately notifies the state changes.
|
||||
* Sets the end target of this gesture and immediately notifies the state
|
||||
* changes.
|
||||
*/
|
||||
public void setEndTarget(GestureEndTarget target) {
|
||||
setEndTarget(target, true /* isAtomic */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the end target of this gesture, but if {@param isAtomic} is {@code false}, then the
|
||||
* caller must explicitly set {@link #STATE_END_TARGET_ANIMATION_FINISHED} themselves.
|
||||
* Sets the end target of this gesture, but if {@param isAtomic} is
|
||||
* {@code false}, then the
|
||||
* caller must explicitly set {@link #STATE_END_TARGET_ANIMATION_FINISHED}
|
||||
* themselves.
|
||||
*/
|
||||
public void setEndTarget(GestureEndTarget target, boolean isAtomic) {
|
||||
mEndTarget = target;
|
||||
@@ -411,7 +456,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the gesture is handling an atomic event like a click and not a
|
||||
* Returns true if the gesture is handling an atomic event like a click and not
|
||||
* a
|
||||
* user controlled gesture.
|
||||
*/
|
||||
public boolean isHandlingAtomicEvent() {
|
||||
@@ -419,7 +465,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the current gesture is still running a recents animation to a state in the
|
||||
* @return whether the current gesture is still running a recents animation to a
|
||||
* state in the
|
||||
* Launcher or Recents activity.
|
||||
*/
|
||||
public boolean isRunningAnimationToLauncher() {
|
||||
@@ -462,15 +509,17 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns and clears the canceled animation thumbnail data. This call only returns a value
|
||||
* while STATE_RECENTS_ANIMATION_CANCELED state is being set, and the caller is responsible for
|
||||
* Returns and clears the canceled animation thumbnail data. This call only
|
||||
* returns a value
|
||||
* while STATE_RECENTS_ANIMATION_CANCELED state is being set, and the caller is
|
||||
* responsible for
|
||||
* calling {@link RecentsAnimationController#cleanupScreenshot()}.
|
||||
*/
|
||||
@Nullable
|
||||
HashMap<Integer, ThumbnailData> consumeRecentsAnimationCanceledSnapshot() {
|
||||
if (mRecentsAnimationCanceledSnapshots != null) {
|
||||
HashMap<Integer, ThumbnailData> data =
|
||||
new HashMap<Integer, ThumbnailData>(mRecentsAnimationCanceledSnapshots);
|
||||
HashMap<Integer, ThumbnailData> data = new HashMap<Integer, ThumbnailData>(
|
||||
mRecentsAnimationCanceledSnapshots);
|
||||
mRecentsAnimationCanceledSnapshots = null;
|
||||
return data;
|
||||
}
|
||||
@@ -490,21 +539,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
pw.println(" gestureID=" + mGestureId);
|
||||
pw.println(" runningTask=" + mRunningTask);
|
||||
pw.println(" endTarget=" + mEndTarget);
|
||||
pw.println(" lastAppearedTaskTargetId=" + getLastAppearedTaskId());
|
||||
pw.println(" lastStartedTaskId=" + mLastStartedTaskId);
|
||||
pw.println(" lastAppearedTaskTargetId=" + Arrays.toString(mLastAppearedTaskTargets));
|
||||
pw.println(" lastStartedTaskId=" + Arrays.toString(mLastStartedTaskId));
|
||||
pw.println(" isRecentsAnimationRunning=" + isRecentsAnimationRunning());
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for winscope tracing, see launcher_trace.proto
|
||||
* @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
|
||||
* @param swipeHandlerProto The parent of this proto message.
|
||||
*/
|
||||
public void writeToProto(SwipeHandlerProto.Builder swipeHandlerProto) {
|
||||
GestureStateProto.Builder gestureStateProto = GestureStateProto.newBuilder();
|
||||
gestureStateProto.setEndTarget(mEndTarget == null
|
||||
? GestureStateProto.GestureEndTarget.UNSET
|
||||
: mEndTarget.protoEndTarget);
|
||||
swipeHandlerProto.setGestureState(gestureStateProto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,6 @@ import android.view.InputEvent;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.launcher3.tracing.InputConsumerProto;
|
||||
import com.android.launcher3.tracing.TouchInteractionServiceProto;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
public interface InputConsumer {
|
||||
|
||||
@@ -42,23 +39,25 @@ public interface InputConsumer {
|
||||
int TYPE_TASKBAR_STASH = 1 << 12;
|
||||
int TYPE_STATUS_BAR = 1 << 13;
|
||||
int TYPE_CURSOR_HOVER = 1 << 14;
|
||||
int TYPE_NAV_HANDLE_LONG_PRESS = 1 << 15;
|
||||
|
||||
String[] NAMES = new String[] {
|
||||
"TYPE_NO_OP", // 0
|
||||
"TYPE_OVERVIEW", // 1
|
||||
"TYPE_OTHER_ACTIVITY", // 2
|
||||
"TYPE_ASSISTANT", // 3
|
||||
"TYPE_DEVICE_LOCKED", // 4
|
||||
"TYPE_ACCESSIBILITY", // 5
|
||||
"TYPE_SCREEN_PINNED", // 6
|
||||
"TYPE_OVERVIEW_WITHOUT_FOCUS", // 7
|
||||
"TYPE_RESET_GESTURE", // 8
|
||||
"TYPE_PROGRESS_DELEGATE", // 9
|
||||
"TYPE_SYSUI_OVERLAY", // 10
|
||||
"TYPE_ONE_HANDED", // 11
|
||||
"TYPE_TASKBAR_STASH", // 12
|
||||
"TYPE_STATUS_BAR", // 13
|
||||
"TYPE_CURSOR_HOVER", // 14
|
||||
"TYPE_NO_OP", // 0
|
||||
"TYPE_OVERVIEW", // 1
|
||||
"TYPE_OTHER_ACTIVITY", // 2
|
||||
"TYPE_ASSISTANT", // 3
|
||||
"TYPE_DEVICE_LOCKED", // 4
|
||||
"TYPE_ACCESSIBILITY", // 5
|
||||
"TYPE_SCREEN_PINNED", // 6
|
||||
"TYPE_OVERVIEW_WITHOUT_FOCUS", // 7
|
||||
"TYPE_RESET_GESTURE", // 8
|
||||
"TYPE_PROGRESS_DELEGATE", // 9
|
||||
"TYPE_SYSUI_OVERLAY", // 10
|
||||
"TYPE_ONE_HANDED", // 11
|
||||
"TYPE_TASKBAR_STASH", // 12
|
||||
"TYPE_STATUS_BAR", // 13
|
||||
"TYPE_CURSOR_HOVER", // 14
|
||||
"TYPE_NAV_HANDLE_LONG_PRESS", // 15
|
||||
};
|
||||
|
||||
InputConsumer NO_OP = () -> TYPE_NO_OP;
|
||||
@@ -66,16 +65,20 @@ public interface InputConsumer {
|
||||
int getType();
|
||||
|
||||
/**
|
||||
* Returns true if the user has crossed the threshold for it to be an explicit action.
|
||||
* Returns true if the user has crossed the threshold for it to be an explicit
|
||||
* action.
|
||||
*/
|
||||
default boolean allowInterceptByParent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the lifecycle of this input consumer is detached from the normal gesture
|
||||
* down/up flow. If so, it is the responsibility of the input consumer to call back to
|
||||
* {@link TouchInteractionService#onConsumerInactive(InputConsumer)} after the consumer is
|
||||
* Returns true if the lifecycle of this input consumer is detached from the
|
||||
* normal gesture
|
||||
* down/up flow. If so, it is the responsibility of the input consumer to call
|
||||
* back to
|
||||
* {@link TouchInteractionService#onConsumerInactive(InputConsumer)} after the
|
||||
* consumer is
|
||||
* finished.
|
||||
*/
|
||||
default boolean isConsumerDetachedFromGesture() {
|
||||
@@ -85,7 +88,8 @@ public interface InputConsumer {
|
||||
/**
|
||||
* Handle and specific setup necessary based on the orientation of the device
|
||||
*/
|
||||
default void notifyOrientationSetup() {}
|
||||
default void notifyOrientationSetup() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the active input consumer is in the hierarchy of this input consumer.
|
||||
@@ -95,17 +99,23 @@ public interface InputConsumer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the event queue when the consumer is about to be switched to a new consumer.
|
||||
* Consumers should update the state accordingly here before the state is passed to the new
|
||||
* Called by the event queue when the consumer is about to be switched to a new
|
||||
* consumer.
|
||||
* Consumers should update the state accordingly here before the state is passed
|
||||
* to the new
|
||||
* consumer.
|
||||
*/
|
||||
default void onConsumerAboutToBeSwitched() { }
|
||||
default void onConsumerAboutToBeSwitched() {
|
||||
}
|
||||
|
||||
default void onMotionEvent(MotionEvent ev) { }
|
||||
default void onMotionEvent(MotionEvent ev) {
|
||||
}
|
||||
|
||||
default void onHoverEvent(MotionEvent ev) { }
|
||||
default void onHoverEvent(MotionEvent ev) {
|
||||
}
|
||||
|
||||
default void onKeyEvent(KeyEvent ev) { }
|
||||
default void onKeyEvent(KeyEvent ev) {
|
||||
}
|
||||
|
||||
default void onInputEvent(InputEvent ev) {
|
||||
if (ev instanceof MotionEvent) {
|
||||
@@ -127,21 +137,4 @@ public interface InputConsumer {
|
||||
}
|
||||
return name.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for winscope tracing, see launcher_trace.proto
|
||||
* @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
|
||||
* @param serviceProto The parent of this proto message.
|
||||
*/
|
||||
default void writeToProto(TouchInteractionServiceProto.Builder serviceProto) {
|
||||
InputConsumerProto.Builder inputConsumerProto = InputConsumerProto.newBuilder();
|
||||
inputConsumerProto.setName(getName());
|
||||
writeToProtoInternal(inputConsumerProto);
|
||||
serviceProto.setInputConsumer(inputConsumerProto);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #writeToProto - allows subclasses to write additional info to the proto.
|
||||
*/
|
||||
default void writeToProtoInternal(InputConsumerProto.Builder inputConsumerProto) {}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.util.InstantAppResolver;
|
||||
@@ -49,4 +52,18 @@ public class InstantAppResolverImpl extends InstantAppResolver {
|
||||
ComponentName cn = info.getTargetComponent();
|
||||
return cn != null && cn.getClassName().equals(COMPONENT_CLASS_MARKER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInstantApp(String packageName, int userId) {
|
||||
if (!Process.myUserHandle().equals(UserHandle.of(userId))) {
|
||||
// Instant app can only exist on current user
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return mPM.isInstantApp(packageName);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to determine whether package is instant app " + packageName, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,13 @@
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.LauncherState.ALL_APPS;
|
||||
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
|
||||
import static com.android.launcher3.LauncherState.FLOATING_SEARCH_BAR;
|
||||
import static com.android.launcher3.LauncherState.NORMAL;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
|
||||
|
||||
@@ -89,10 +90,14 @@ public final class LauncherActivityInterface extends
|
||||
if (launcher == null) {
|
||||
return;
|
||||
}
|
||||
// When going to home, the state animator we use has SKIP_OVERVIEW because we assume that
|
||||
// setRecentsAttachedToAppWindow() will handle animating Overview instead. Thus, at the end
|
||||
// of the animation, we should ensure recents is at the correct position for NORMAL state.
|
||||
// For example, when doing a long swipe to home, RecentsView may be scaled down. This is
|
||||
// When going to home, the state animator we use has SKIP_OVERVIEW because we
|
||||
// assume that
|
||||
// setRecentsAttachedToAppWindow() will handle animating Overview instead. Thus,
|
||||
// at the end
|
||||
// of the animation, we should ensure recents is at the correct position for
|
||||
// NORMAL state.
|
||||
// For example, when doing a long swipe to home, RecentsView may be scaled down.
|
||||
// This is
|
||||
// relatively expensive, so do it on the next frame instead of critical path.
|
||||
MAIN_EXECUTOR.getHandler().post(launcher.getStateManager()::reapplyState);
|
||||
|
||||
@@ -138,8 +143,7 @@ public final class LauncherActivityInterface extends
|
||||
|
||||
@Override
|
||||
public ActivityInitListener createActivityInitListener(Predicate<Boolean> onInitListener) {
|
||||
return new LauncherInitListener((activity, alreadyOnHome) ->
|
||||
onInitListener.test(alreadyOnHome));
|
||||
return new LauncherInitListener((activity, alreadyOnHome) -> onInitListener.test(alreadyOnHome));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -191,9 +195,9 @@ public final class LauncherActivityInterface extends
|
||||
@Override
|
||||
public RecentsView getVisibleRecentsView() {
|
||||
Launcher launcher = getVisibleLauncher();
|
||||
RecentsView recentsView =
|
||||
launcher != null && launcher.getStateManager().getState().overviewUi
|
||||
? launcher.getOverviewPanel() : null;
|
||||
RecentsView recentsView = launcher != null && launcher.getStateManager().getState().overviewUi
|
||||
? launcher.getOverviewPanel()
|
||||
: null;
|
||||
if (recentsView == null || (!launcher.hasBeenResumed()
|
||||
&& recentsView.getRunningTaskViewId() == -1)) {
|
||||
// If live tile has ended, return null.
|
||||
@@ -230,7 +234,6 @@ public final class LauncherActivityInterface extends
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
|
||||
final StateManager<LauncherState> stateManager = getCreatedActivity().getStateManager();
|
||||
@@ -271,7 +274,10 @@ public final class LauncherActivityInterface extends
|
||||
|
||||
@Override
|
||||
public boolean allowAllAppsFromOverview() {
|
||||
return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get();
|
||||
return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()
|
||||
// If floating search bar would not show in overview, don't allow all apps
|
||||
// gesture.
|
||||
&& OVERVIEW.areElementsVisible(getCreatedActivity(), FLOATING_SEARCH_BAR);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -111,7 +111,7 @@ public class LauncherBackAnimationController {
|
||||
private boolean mBackInProgress = false;
|
||||
private IOnBackInvokedCallback mBackCallback;
|
||||
private IRemoteAnimationFinishedCallback mAnimationFinishedCallback;
|
||||
private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
|
||||
private BackProgressAnimator mProgressAnimator;
|
||||
private SurfaceControl mScrimLayer;
|
||||
private ValueAnimator mScrimAlphaAnimator;
|
||||
private float mScrimAlpha;
|
||||
@@ -132,12 +132,13 @@ public class LauncherBackAnimationController {
|
||||
R.dimen.swipe_back_window_scale_x_margin);
|
||||
mWindowMaxDeltaY = mLauncher.getResources().getDimensionPixelSize(
|
||||
R.dimen.swipe_back_window_max_delta_y);
|
||||
mCancelInterpolator = AnimationUtils.loadInterpolator(mLauncher, R.interpolator.back_cancel);
|
||||
mCancelInterpolator = AnimationUtils.loadInterpolator(mLauncher, R.interpolator.standard_interpolator);
|
||||
try {
|
||||
mProgressAnimator = new BackProgressAnimator();
|
||||
} catch (Throwable throwable) {
|
||||
|
||||
// ignore
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,74 +148,83 @@ public class LauncherBackAnimationController {
|
||||
* @param handler Handler to the thread to run the animations on.
|
||||
*/
|
||||
public void registerBackCallbacks(Handler handler) {
|
||||
mBackCallback = new IOnBackInvokedCallback.Stub() {
|
||||
@Override
|
||||
public void onBackStarted(BackMotionEvent backMotionEvent) {
|
||||
startBack (backMotionEvent);
|
||||
handler.post(() -> {
|
||||
if (mProgressAnimator == null) {
|
||||
return;
|
||||
}
|
||||
mProgressAnimator.onBackStarted(backMotionEvent, event -> {
|
||||
mBackProgress = event.getProgress();
|
||||
// TODO: Update once the interpolation curve spec is finalized.
|
||||
mBackProgress = 1 - (1 - mBackProgress) * (1 - mBackProgress) * (1
|
||||
- mBackProgress);
|
||||
updateBackProgress(mBackProgress, event);
|
||||
try {
|
||||
mBackCallback = new IOnBackInvokedCallback.Stub() {
|
||||
@Override
|
||||
public void onBackStarted(BackMotionEvent backMotionEvent) {
|
||||
startBack(backMotionEvent);
|
||||
handler.post(() -> {
|
||||
if (mProgressAnimator == null) {
|
||||
return;
|
||||
}
|
||||
mProgressAnimator.onBackStarted(backMotionEvent, event -> {
|
||||
mBackProgress = event.getProgress();
|
||||
// TODO: Update once the interpolation curve spec is finalized.
|
||||
mBackProgress = 1 - (1 - mBackProgress) * (1 - mBackProgress) * (1
|
||||
- mBackProgress);
|
||||
updateBackProgress(mBackProgress, event);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackProgressed(BackMotionEvent backMotionEvent) {
|
||||
handler.post(() -> {
|
||||
if (mProgressAnimator == null) {
|
||||
return;
|
||||
}
|
||||
mProgressAnimator.onBackProgressed(backMotionEvent);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackCancelled() {
|
||||
handler.post(() -> {
|
||||
mProgressAnimator.onBackCancelled(
|
||||
LauncherBackAnimationController.this::resetPositionAnimated);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackInvoked() {
|
||||
handler.post(() -> {
|
||||
startTransition();
|
||||
if (mProgressAnimator == null) {
|
||||
return;
|
||||
}
|
||||
mProgressAnimator.reset();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
final IRemoteAnimationRunner runner = new IRemoteAnimationRunner.Stub() {
|
||||
@Override
|
||||
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
|
||||
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
|
||||
IRemoteAnimationFinishedCallback finishedCallback) {
|
||||
for (final RemoteAnimationTarget target : apps) {
|
||||
if (MODE_CLOSING == target.mode) {
|
||||
mBackTarget = target;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mAnimationFinishedCallback = finishedCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancelled() {
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public void onBackProgressed(BackMotionEvent backMotionEvent) {
|
||||
handler.post(() -> {
|
||||
if (mProgressAnimator == null) {
|
||||
return;
|
||||
}
|
||||
mProgressAnimator.onBackProgressed(backMotionEvent);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackCancelled() {
|
||||
handler.post(() -> {
|
||||
if (mProgressAnimator == null) {
|
||||
return;
|
||||
}
|
||||
mProgressAnimator.onBackCancelled(
|
||||
LauncherBackAnimationController.this::resetPositionAnimated);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackInvoked() {
|
||||
handler.post(() -> {
|
||||
startTransition();
|
||||
if (mProgressAnimator == null) {
|
||||
return;
|
||||
}
|
||||
mProgressAnimator.reset();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
final IRemoteAnimationRunner runner = new IRemoteAnimationRunner.Stub() {
|
||||
@Override
|
||||
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
|
||||
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
|
||||
IRemoteAnimationFinishedCallback finishedCallback) {
|
||||
for (final RemoteAnimationTarget target : apps) {
|
||||
if (MODE_CLOSING == target.mode) {
|
||||
mBackTarget = target;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mAnimationFinishedCallback = finishedCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancelled() {
|
||||
}
|
||||
};
|
||||
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(mBackCallback, runner);
|
||||
|
||||
} catch (Throwable t) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(mBackCallback, runner);
|
||||
}
|
||||
|
||||
private void resetPositionAnimated() {
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
*/
|
||||
package com.android.quickstep;
|
||||
|
||||
import static com.android.app.animation.Interpolators.EXAGGERATED_EASE;
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.LauncherState.NORMAL;
|
||||
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.model.data.ItemInfo.NO_MATCHING_ID;
|
||||
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
|
||||
import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user