Merge branch 'heads/android-14.0.0_r18' into 14-dev

This commit is contained in:
MrSluffy
2023-12-14 15:26:43 +08:00
parent 32700b0100
commit cc8f9828ab
756 changed files with 23785 additions and 12235 deletions
@@ -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];
}
};
}
@@ -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();
}
};
@@ -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.
@@ -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;
}
}
@@ -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();
}
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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
@@ -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);
}
}
}
@@ -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
@@ -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(),
@@ -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,
@@ -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
@@ -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));
}
@@ -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;
}
@@ -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);
@@ -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) {
@@ -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