Merge Android 24Q1 Release (ab/11220357)

Bug: 319669529
Merged-In: I0708bf3c060ba84089722d0bd9480a4f4bd2b8e2
Change-Id: I31bb7f6aa8f71244f6e44903927f67e9d3a85642
This commit is contained in:
Xin Li
2024-01-18 13:56:04 -08:00
833 changed files with 34863 additions and 15561 deletions
@@ -0,0 +1,57 @@
/*
* 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;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import androidx.annotation.Nullable;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.quickstep.SystemUiProxy;
import com.android.wm.shell.transition.IHomeTransitionListener;
/**
* Controls launcher response to home activity visibility changing.
*/
public class HomeTransitionController {
private final QuickstepLauncher mLauncher;
@Nullable private IHomeTransitionListener mHomeTransitionListener;
public HomeTransitionController(QuickstepLauncher launcher) {
mLauncher = launcher;
}
public void registerHomeTransitionListener() {
mHomeTransitionListener = new IHomeTransitionListener.Stub() {
@Override
public void onHomeVisibilityChanged(boolean isVisible) {
MAIN_EXECUTOR.execute(() -> {
if (mLauncher.getTaskbarUIController() != null) {
mLauncher.getTaskbarUIController().onLauncherVisibilityChanged(isVisible);
}
});
}
};
SystemUiProxy.INSTANCE.get(mLauncher).setHomeTransitionListener(mHomeTransitionListener);
}
public void unregisterHomeTransitionListener() {
SystemUiProxy.INSTANCE.get(mLauncher).setHomeTransitionListener(null);
mHomeTransitionListener = null;
}
}
@@ -22,6 +22,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -53,7 +55,9 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_SCRIM_FOR_APP_LAU
import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.testing.shared.TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE;
import static com.android.launcher3.util.DisplayController.isTransientTaskbar;
import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
@@ -74,8 +78,8 @@ import android.app.ActivityOptions;
import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Point;
@@ -90,6 +94,7 @@ import android.os.Looper;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.util.Pair;
import android.util.Size;
import android.view.CrossWindowBlurListeners;
@@ -116,6 +121,7 @@ import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.model.data.ItemInfo;
@@ -160,6 +166,7 @@ import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.wm.shell.startingsurface.IStartingWindowListener;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -181,9 +188,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
*/
public static final int STATUS_BAR_TRANSITION_PRE_DELAY = 96;
private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
"android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
public static final long APP_LAUNCH_DURATION = 500;
private static final long APP_LAUNCH_ALPHA_DURATION = 50;
@@ -226,7 +230,18 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
private final float mClosingWindowTransY;
private final float mMaxShadowRadius;
private final StartingWindowListener mStartingWindowListener = new StartingWindowListener();
private final StartingWindowListener mStartingWindowListener =
new StartingWindowListener(this);
private ContentObserver mAnimationRemovalObserver = new ContentObserver(
ORDERED_BG_EXECUTOR.getHandler()) {
@Override
public void onChange(boolean selfChange) {
mAreAnimationsEnabled = Global.getFloat(mLauncher.getContentResolver(),
Global.ANIMATOR_DURATION_SCALE, 1f) > 0
|| Global.getFloat(mLauncher.getContentResolver(),
Global.TRANSITION_ANIMATION_SCALE, 1f) > 0;
}
};
private DeviceProfile mDeviceProfile;
@@ -238,6 +253,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
private RemoteAnimationFactory mWallpaperOpenTransitionRunner;
private RemoteTransition mLauncherOpenTransition;
private final RemoteAnimationCoordinateTransfer mCoordinateTransfer;
private LauncherBackAnimationController mBackAnimationController;
private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() {
@Override
@@ -254,6 +271,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
// Pairs of window starting type and starting window background color for starting tasks
// Will never be larger than MAX_NUM_TASKS
private LinkedHashMap<Integer, Pair<Integer, Integer>> mTaskStartParams;
private boolean mAreAnimationsEnabled = true;
private final Interpolator mOpeningXInterpolator;
private final Interpolator mOpeningInterpolator;
@@ -264,6 +282,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
mHandler = new Handler(Looper.getMainLooper());
mDeviceProfile = mLauncher.getDeviceProfile();
mBackAnimationController = new LauncherBackAnimationController(mLauncher, this);
checkAndMonitorIfAnimationsAreEnabled();
Resources res = mLauncher.getResources();
mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
@@ -271,15 +290,14 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
mLauncher.addOnDeviceProfileChangeListener(this);
if (supportsSSplashScreen()) {
mTaskStartParams = new LinkedHashMap<Integer, Pair<Integer, Integer>>(MAX_NUM_TASKS) {
if (ENABLE_SHELL_STARTING_SURFACE) {
mTaskStartParams = new LinkedHashMap<>(MAX_NUM_TASKS) {
@Override
protected boolean removeEldestEntry(Entry<Integer, Pair<Integer, Integer>> entry) {
return size() > MAX_NUM_TASKS;
}
};
mStartingWindowListener.setTransitionManager(this);
SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(
mStartingWindowListener);
}
@@ -287,6 +305,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
mOpeningXInterpolator = AnimationUtils.loadInterpolator(context, R.interpolator.app_open_x);
mOpeningInterpolator = AnimationUtils.loadInterpolator(context,
R.interpolator.emphasized_interpolator);
mCoordinateTransfer = new RemoteAnimationCoordinateTransfer(mLauncher);
}
@Override
@@ -311,8 +330,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
mAppLaunchRunner = new AppLaunchAnimationRunner(v, onEndCallback);
ItemInfo tag = (ItemInfo) v.getTag();
if (tag != null && tag.shouldUseBackgroundAnimation()) {
ContainerAnimationRunner containerAnimationRunner =
ContainerAnimationRunner.from(v, mStartingWindowListener, onEndCallback);
ContainerAnimationRunner containerAnimationRunner = ContainerAnimationRunner.from(
v, mLauncher, mStartingWindowListener, onEndCallback);
if (containerAnimationRunner != null) {
mAppLaunchRunner = containerAnimationRunner;
}
@@ -474,9 +493,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
/**
* Content is everything on screen except the background and the floating view (if any).
*
* @param isAppOpening True when this is called when an app is opening.
* False when this is called when an app is closing.
* @param startDelay Start delay duration.
* @param isAppOpening True when this is called when an app is opening.
* False when this is called when an app is closing.
* @param startDelay Start delay duration.
* @param skipAllAppsScale True if we want to avoid scaling All Apps
*/
private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening,
@@ -660,7 +679,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
mDragLayer.getLocationOnScreen(dragLayerBounds);
final boolean hasSplashScreen;
if (supportsSSplashScreen()) {
if (ENABLE_SHELL_STARTING_SURFACE) {
int taskId = openingTargets.getFirstAppTargetTaskId();
Pair<Integer, Integer> defaultParams = Pair.create(STARTING_WINDOW_TYPE_NONE, 0);
Pair<Integer, Integer> taskParams =
@@ -905,7 +924,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
RemoteAnimationTarget openingTarget = openingTargets.getFirstAppTarget();
int fallbackBackgroundColor = 0;
if (openingTarget != null && supportsSSplashScreen()) {
if (openingTarget != null && ENABLE_SHELL_STARTING_SURFACE) {
fallbackBackgroundColor = mTaskStartParams.containsKey(openingTarget.taskId)
? mTaskStartParams.get(openingTarget.taskId).second : 0;
mTaskStartParams.remove(openingTarget.taskId);
@@ -1043,7 +1062,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
LaunchDepthController depthController = new LaunchDepthController(mLauncher);
ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController.stateDepth,
MULTI_PROPERTY_VALUE, BACKGROUND_APP.getDepth(mLauncher))
.setDuration(APP_LAUNCH_DURATION);
.setDuration(APP_LAUNCH_DURATION);
if (allowBlurringLauncher) {
// Create a temporary effect layer, that lives on top of launcher, so we can apply
@@ -1081,11 +1100,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
if (SEPARATE_RECENTS_ACTIVITY.get()) {
return;
}
if (hasControlRemoteAppTransitionPermission()) {
RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
addRemoteAnimations(definition);
mLauncher.registerRemoteAnimations(definition);
}
RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
addRemoteAnimations(definition);
mLauncher.registerRemoteAnimations(definition);
}
/**
@@ -1123,28 +1140,27 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
if (SEPARATE_RECENTS_ACTIVITY.get()) {
return;
}
if (hasControlRemoteAppTransitionPermission()) {
mWallpaperOpenTransitionRunner = createWallpaperOpenRunner(false /* fromUnlock */);
mLauncherOpenTransition = new RemoteTransition(
new LauncherAnimationRunner(mHandler, mWallpaperOpenTransitionRunner,
false /* startAtFrontOfQueue */).toRemoteTransition(),
mLauncher.getIApplicationThread(), "QuickstepLaunchHome");
TransitionFilter homeCheck = new TransitionFilter();
// No need to handle the transition that also dismisses keyguard.
homeCheck.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
homeCheck.mRequirements =
new TransitionFilter.Requirement[]{new TransitionFilter.Requirement(),
new TransitionFilter.Requirement()};
homeCheck.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME;
homeCheck.mRequirements[0].mTopActivity = mLauncher.getComponentName();
homeCheck.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
homeCheck.mRequirements[0].mOrder = CONTAINER_ORDER_TOP;
homeCheck.mRequirements[1].mActivityType = ACTIVITY_TYPE_STANDARD;
homeCheck.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
SystemUiProxy.INSTANCE.get(mLauncher)
.registerRemoteTransition(mLauncherOpenTransition, homeCheck);
}
mWallpaperOpenTransitionRunner = createWallpaperOpenRunner(false /* fromUnlock */);
mLauncherOpenTransition = new RemoteTransition(
new LauncherAnimationRunner(mHandler, mWallpaperOpenTransitionRunner,
false /* startAtFrontOfQueue */).toRemoteTransition(),
mLauncher.getIApplicationThread(), "QuickstepLaunchHome");
TransitionFilter homeCheck = new TransitionFilter();
// No need to handle the transition that also dismisses keyguard.
homeCheck.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
homeCheck.mRequirements =
new TransitionFilter.Requirement[]{new TransitionFilter.Requirement(),
new TransitionFilter.Requirement()};
homeCheck.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME;
homeCheck.mRequirements[0].mTopActivity = mLauncher.getComponentName();
homeCheck.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
homeCheck.mRequirements[0].mOrder = CONTAINER_ORDER_TOP;
homeCheck.mRequirements[1].mActivityType = ACTIVITY_TYPE_STANDARD;
homeCheck.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
SystemUiProxy.INSTANCE.get(mLauncher)
.registerRemoteTransition(mLauncherOpenTransition, homeCheck);
if (mBackAnimationController != null) {
mBackAnimationController.registerBackCallbacks(mHandler);
}
@@ -1153,23 +1169,22 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
public void onActivityDestroyed() {
unregisterRemoteAnimations();
unregisterRemoteTransitions();
mStartingWindowListener.setTransitionManager(null);
SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null);
ORDERED_BG_EXECUTOR.execute(() -> mLauncher.getContentResolver()
.unregisterContentObserver(mAnimationRemovalObserver));
}
private void unregisterRemoteAnimations() {
if (SEPARATE_RECENTS_ACTIVITY.get()) {
return;
}
if (hasControlRemoteAppTransitionPermission()) {
mLauncher.unregisterRemoteAnimations();
mLauncher.unregisterRemoteAnimations();
// Also clear strong references to the runners registered with the remote animation
// definition so we don't have to wait for the system gc
mWallpaperOpenRunner = null;
mAppLaunchRunner = null;
mKeyguardGoingAwayRunner = null;
}
// Also clear strong references to the runners registered with the remote animation
// definition so we don't have to wait for the system gc
mWallpaperOpenRunner = null;
mAppLaunchRunner = null;
mKeyguardGoingAwayRunner = null;
}
protected void unregisterRemoteTransitions() {
@@ -1179,19 +1194,28 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
if (SEPARATE_RECENTS_ACTIVITY.get()) {
return;
}
if (hasControlRemoteAppTransitionPermission()) {
if (mLauncherOpenTransition == null) return;
SystemUiProxy.INSTANCE.get(mLauncher).unregisterRemoteTransition(
mLauncherOpenTransition);
mLauncherOpenTransition = null;
mWallpaperOpenTransitionRunner = null;
}
if (mLauncherOpenTransition == null) return;
SystemUiProxy.INSTANCE.get(mLauncher).unregisterRemoteTransition(
mLauncherOpenTransition);
mLauncherOpenTransition = null;
mWallpaperOpenTransitionRunner = null;
if (mBackAnimationController != null) {
mBackAnimationController.unregisterBackCallbacks();
mBackAnimationController = null;
}
}
private void checkAndMonitorIfAnimationsAreEnabled() {
ORDERED_BG_EXECUTOR.execute(() -> {
mAnimationRemovalObserver.onChange(true);
mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
Global.ANIMATOR_DURATION_SCALE), false, mAnimationRemovalObserver);
mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
Global.TRANSITION_ANIMATION_SCALE), false, mAnimationRemovalObserver);
});
}
private boolean launcherIsATargetWithMode(RemoteAnimationTarget[] targets, int mode) {
for (RemoteAnimationTarget target : targets) {
if (target.mode == mode && target.taskInfo != null
@@ -1287,7 +1311,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
return null;
}
final ComponentName[] taskInfoActivities = new ComponentName[] {
final ComponentName[] taskInfoActivities = new ComponentName[]{
runningTaskTarget.taskInfo.baseActivity,
runningTaskTarget.taskInfo.origActivity,
runningTaskTarget.taskInfo.realActivity,
@@ -1333,7 +1357,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
.getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx);
float secondaryDimension = orientationHandler
.getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx);
final float targetX = primaryDimension / 2f;
final float targetX = primaryDimension / 2f;
final float targetY = secondaryDimension - dp.hotseatBarSizePx;
return new RectF(targetX - halfIconSize, targetY - halfIconSize,
targetX + halfIconSize, targetY + halfIconSize);
@@ -1344,7 +1368,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
*/
protected RectFSpringAnim getClosingWindowAnimators(AnimatorSet animation,
RemoteAnimationTarget[] targets, View launcherView, PointF velocityPxPerS,
RectF closingWindowStartRect, float startWindowCornerRadius) {
RectF closingWindowStartRectF, float startWindowCornerRadius) {
FloatingIconView floatingIconView = null;
FloatingWidgetView floatingWidget = null;
RectF targetRect = new RectF();
@@ -1370,7 +1394,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
(LauncherAppWidgetHostView) launcherView, targetRect, windowSize,
mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher),
isTransluscent, fallbackBackgroundColor);
} else if (launcherView != null) {
} else if (launcherView != null && mAreAnimationsEnabled) {
floatingIconView = getFloatingIconView(mLauncher, launcherView, null,
mLauncher.getTaskbarUIController() == null
? null
@@ -1384,13 +1408,16 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
boolean useTaskbarHotseatParams = mDeviceProfile.isTaskbarPresent && isInHotseat;
RectFSpringAnim anim = new RectFSpringAnim(useTaskbarHotseatParams
? new TaskbarHotseatSpringConfig(mLauncher, closingWindowStartRect, targetRect)
: new DefaultSpringConfig(mLauncher, mDeviceProfile, closingWindowStartRect,
? new TaskbarHotseatSpringConfig(mLauncher, closingWindowStartRectF, targetRect)
: new DefaultSpringConfig(mLauncher, mDeviceProfile, closingWindowStartRectF,
targetRect));
// Hook up floating views to the closing window animators.
final int rotationChange = getRotationChange(targets);
Rect windowTargetBounds = getWindowTargetBounds(targets, rotationChange);
// note the coordinate of closingWindowStartRect is based on launcher
Rect closingWindowStartRect = new Rect();
closingWindowStartRectF.round(closingWindowStartRect);
Rect closingWindowOriginalRect =
new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);
if (floatingIconView != null) {
anim.addAnimatorListener(floatingIconView);
floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged);
@@ -1402,7 +1429,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
final float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION;
RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
windowTargetBounds, startWindowCornerRadius) {
closingWindowStartRect, closingWindowOriginalRect, startWindowCornerRadius) {
@Override
public void onUpdate(RectF currentRectF, float progress) {
finalFloatingIconView.update(1f, currentRectF, progress, windowAlphaThreshold,
@@ -1420,7 +1447,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
final float floatingWidgetAlpha = isTransluscent ? 0 : 1;
FloatingWidgetView finalFloatingWidget = floatingWidget;
RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
windowTargetBounds, startWindowCornerRadius) {
closingWindowStartRect, closingWindowOriginalRect, startWindowCornerRadius) {
@Override
public void onUpdate(RectF currentRectF, float progress) {
final float fallbackBackgroundAlpha =
@@ -1438,7 +1465,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
// If no floating icon or widget is present, animate the to the default window
// target rect.
anim.addOnUpdateListener(new SpringAnimRunner(
targets, targetRect, windowTargetBounds, startWindowCornerRadius));
targets, targetRect, closingWindowStartRect, closingWindowOriginalRect,
startWindowCornerRadius));
}
// Use a fixed velocity to start the animation.
@@ -1519,20 +1547,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
return closingAnimator;
}
private boolean supportsSSplashScreen() {
return hasControlRemoteAppTransitionPermission()
&& Utilities.ATLEAST_S
&& ENABLE_SHELL_STARTING_SURFACE;
}
/**
* Returns true if we have permission to control remote app transisions
*/
public boolean hasControlRemoteAppTransitionPermission() {
return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
== PackageManager.PERMISSION_GRANTED;
}
private void addCujInstrumentation(Animator anim, int cuj) {
anim.addListener(new AnimationSuccessListener() {
@Override
@@ -1638,7 +1652,17 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
// is initialized.
if (launcherIsForceInvisibleOrOpening) {
addCujInstrumentation(anim, playFallBackAnimation
? CUJ_APP_CLOSE_TO_HOME_FALLBACK : CUJ_APP_CLOSE_TO_HOME);
? CUJ_APP_CLOSE_TO_HOME_FALLBACK : CUJ_APP_CLOSE_TO_HOME);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
AccessibilityManagerCompat.sendTestProtocolEventToTest(
mLauncher, WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE);
}
});
// Only register the content animation for cancellation when state changes
mLauncher.getStateManager().setCurrentAnimation(anim);
@@ -1694,8 +1718,18 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
RectF windowTargetBounds =
new RectF(getWindowTargetBounds(appTargets, getRotationChange(appTargets)));
final RectF resolveRectF = new RectF(windowTargetBounds);
for (RemoteAnimationTarget t : appTargets) {
if (t.mode == MODE_CLOSING) {
transferRectToTargetCoordinate(
t, windowTargetBounds, true, resolveRectF);
break;
}
}
Pair<RectFSpringAnim, AnimatorSet> pair = createWallpaperOpenAnimations(
appTargets, wallpaperTargets, mFromUnlock, windowTargetBounds,
appTargets, wallpaperTargets, mFromUnlock, resolveRectF,
QuickStepContract.getWindowCornerRadius(mLauncher),
false /* fromPredictiveBack */);
@@ -1776,11 +1810,11 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
}
@Nullable
private static ContainerAnimationRunner from(
View v, StartingWindowListener startingWindowListener, RunnableList onEndCallback) {
private static ContainerAnimationRunner from(View v, Launcher launcher,
StartingWindowListener startingWindowListener, RunnableList onEndCallback) {
View viewToUse = findLaunchableViewWithBackground(v);
if (viewToUse == null) {
viewToUse = v;
return null;
}
// The CUJ is logged by the click handler, so we don't log it inside the animation
@@ -1802,8 +1836,13 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
}
};
ActivityLaunchAnimator.Callback callback = task -> ColorUtils.setAlphaComponent(
startingWindowListener.getBackgroundColor(), 255);
ActivityLaunchAnimator.Callback callback = task -> {
final int backgroundColor =
startingWindowListener.mBackgroundColor == Color.TRANSPARENT
? launcher.getScrimView().getBackgroundColor()
: startingWindowListener.mBackgroundColor;
return ColorUtils.setAlphaComponent(backgroundColor, 255);
};
ActivityLaunchAnimator.Listener listener = new ActivityLaunchAnimator.Listener() {
@Override
@@ -1917,24 +1956,68 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
}
}
private class StartingWindowListener extends IStartingWindowListener.Stub {
private QuickstepTransitionManager mTransitionManager;
private static class StartingWindowListener extends IStartingWindowListener.Stub {
private final WeakReference<QuickstepTransitionManager> mTransitionManagerRef;
private int mBackgroundColor;
public void setTransitionManager(QuickstepTransitionManager transitionManager) {
mTransitionManager = transitionManager;
private StartingWindowListener(QuickstepTransitionManager transitionManager) {
mTransitionManagerRef = new WeakReference<>(transitionManager);
}
@Override
public void onTaskLaunching(int taskId, int supportedType, int color) {
mTransitionManager.mTaskStartParams.put(taskId, Pair.create(supportedType, color));
QuickstepTransitionManager transitionManager = mTransitionManagerRef.get();
if (transitionManager != null) {
transitionManager.mTaskStartParams.put(taskId, Pair.create(supportedType, color));
}
mBackgroundColor = color;
}
}
public int getBackgroundColor() {
return mBackgroundColor == Color.TRANSPARENT
? mLauncher.getScrimView().getBackgroundColor()
: mBackgroundColor;
/**
* Transfer the rectangle to another coordinate if needed.
*
* @param toLauncher which one is the anchor of this transfer, if true then transfer from
* animation target to launcher, false transfer from launcher to animation
* target.
*/
public void transferRectToTargetCoordinate(RemoteAnimationTarget target, RectF currentRect,
boolean toLauncher, RectF resultRect) {
mCoordinateTransfer.transferRectToTargetCoordinate(
target, currentRect, toLauncher, resultRect);
}
private static class RemoteAnimationCoordinateTransfer {
private final QuickstepLauncher mLauncher;
private final Rect mDisplayRect = new Rect();
private final Rect mTmpResult = new Rect();
RemoteAnimationCoordinateTransfer(QuickstepLauncher launcher) {
mLauncher = launcher;
}
void transferRectToTargetCoordinate(RemoteAnimationTarget target, RectF currentRect,
boolean toLauncher, RectF resultRect) {
final int taskRotation = target.windowConfiguration.getRotation();
final DeviceProfile profile = mLauncher.getDeviceProfile();
final int rotationDelta = toLauncher
? android.util.RotationUtils.deltaRotation(taskRotation, profile.rotationHint)
: android.util.RotationUtils.deltaRotation(profile.rotationHint, taskRotation);
if (rotationDelta != ROTATION_0) {
// Get original display size when task is on top but with different rotation
if (rotationDelta % 2 != 0 && toLauncher && (profile.rotationHint == ROTATION_0
|| profile.rotationHint == ROTATION_180)) {
mDisplayRect.set(0, 0, profile.heightPx, profile.widthPx);
} else {
mDisplayRect.set(0, 0, profile.widthPx, profile.heightPx);
}
currentRect.round(mTmpResult);
android.util.RotationUtils.rotateBounds(mTmpResult, mDisplayRect, rotationDelta);
resultRect.set(mTmpResult);
} else {
resultRect.set(currentRect);
}
}
}
@@ -1949,17 +2032,49 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
private final float mStartRadius;
private final float mEndRadius;
private final SurfaceTransactionApplier mSurfaceApplier;
private final Rect mWindowTargetBounds = new Rect();
private final Rect mWindowStartBounds = new Rect();
private final Rect mWindowOriginalBounds = new Rect();
private final Rect mTmpRect = new Rect();
/**
* Constructor for SpringAnimRunner
*
* @param appTargets the list of opening/closing apps
* @param targetRect target rectangle
* @param closingWindowStartRect start position of the window when the spring animation
* is started. In the predictive back to home case this
* will be smaller than closingWindowOriginalRect because
* the window is already scaled by the user gesture
* @param closingWindowOriginalRect Original unscaled window rect
* @param startWindowCornerRadius corner radius of window at the start position
*/
SpringAnimRunner(RemoteAnimationTarget[] appTargets, RectF targetRect,
Rect windowTargetBounds, float startWindowCornerRadius) {
Rect closingWindowStartRect, Rect closingWindowOriginalRect,
float startWindowCornerRadius) {
mAppTargets = appTargets;
mStartRadius = startWindowCornerRadius;
mEndRadius = Math.max(1, targetRect.width()) / 2f;
mSurfaceApplier = new SurfaceTransactionApplier(mDragLayer);
mWindowTargetBounds.set(windowTargetBounds);
mWindowStartBounds.set(closingWindowStartRect);
mWindowOriginalBounds.set(closingWindowOriginalRect);
// transfer the coordinate based on animation target.
if (mAppTargets != null) {
for (RemoteAnimationTarget t : mAppTargets) {
if (t.mode == MODE_CLOSING) {
final RectF transferRect = new RectF(mWindowStartBounds);
final RectF result = new RectF();
transferRectToTargetCoordinate(t, transferRect, false, result);
result.round(mWindowStartBounds);
transferRect.set(closingWindowOriginalRect);
transferRectToTargetCoordinate(t, transferRect, false, result);
result.round(mWindowOriginalBounds);
break;
}
}
}
}
public float getCornerRadius(float progress) {
@@ -1980,26 +2095,28 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
}
if (target.mode == MODE_CLOSING) {
transferRectToTargetCoordinate(target, currentRectF, false, currentRectF);
currentRectF.round(mCurrentRect);
// Scale the target window to match the currentRectF.
final float scale;
// We need to infer the crop (we crop the window to match the currentRectF).
if (mWindowTargetBounds.height() > mWindowTargetBounds.width()) {
scale = Math.min(1f, currentRectF.width() / mWindowTargetBounds.width());
if (mWindowStartBounds.height() > mWindowStartBounds.width()) {
scale = Math.min(1f, currentRectF.width() / mWindowOriginalBounds.width());
int unscaledHeight = (int) (mCurrentRect.height() * (1f / scale));
int croppedHeight = mWindowTargetBounds.height() - unscaledHeight;
mTmpRect.set(0, 0, mWindowTargetBounds.width(),
mWindowTargetBounds.height() - croppedHeight);
int croppedHeight = mWindowStartBounds.height() - unscaledHeight;
mTmpRect.set(0, 0, mWindowOriginalBounds.width(),
mWindowStartBounds.height() - croppedHeight);
} else {
scale = Math.min(1f, currentRectF.height() / mWindowTargetBounds.height());
scale = Math.min(1f, currentRectF.height()
/ mWindowOriginalBounds.height());
int unscaledWidth = (int) (mCurrentRect.width() * (1f / scale));
int croppedWidth = mWindowTargetBounds.width() - unscaledWidth;
mTmpRect.set(0, 0, mWindowTargetBounds.width() - croppedWidth,
mWindowTargetBounds.height());
int croppedWidth = mWindowStartBounds.width() - unscaledWidth;
mTmpRect.set(0, 0, mWindowStartBounds.width() - croppedWidth,
mWindowOriginalBounds.height());
}
// Match size and position of currentRect.
@@ -0,0 +1,93 @@
/*
* 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;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.os.Bundle;
import android.view.WindowInsetsController;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import com.android.launcher3.dragndrop.SimpleDragLayer;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.widget.BaseWidgetSheet;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
import java.util.ArrayList;
/** An Activity that can host Launcher's widget picker. */
public class WidgetPickerActivity extends BaseActivity {
private SimpleDragLayer<WidgetPickerActivity> mDragLayer;
private WidgetsModel mModel;
private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
LauncherAppState app = LauncherAppState.getInstance(this);
InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
mDeviceProfile = idp.getDeviceProfile(this);
mModel = new WidgetsModel();
setContentView(R.layout.widget_picker_activity);
mDragLayer = findViewById(R.id.drag_layer);
mDragLayer.recreateControllers();
WindowInsetsController wc = mDragLayer.getWindowInsetsController();
wc.hide(navigationBars() + statusBars());
BaseWidgetSheet widgetSheet = WidgetsFullSheet.show(this, true);
widgetSheet.disableNavBarScrim(true);
widgetSheet.addOnCloseListener(this::finish);
refreshAndBindWidgets();
}
@NonNull
@Override
public PopupDataProvider getPopupDataProvider() {
return mPopupDataProvider;
}
@Override
public SimpleDragLayer<WidgetPickerActivity> getDragLayer() {
return mDragLayer;
}
private void refreshAndBindWidgets() {
MODEL_EXECUTOR.execute(() -> {
LauncherAppState app = LauncherAppState.getInstance(this);
mModel.update(app, null);
final ArrayList<WidgetsListBaseEntry> widgets =
mModel.getWidgetsListForPicker(app.getContext());
MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets));
});
}
}
@@ -35,9 +35,7 @@ import androidx.core.content.ContextCompat;
import com.android.launcher3.R;
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
/**
* A view which shows a horizontal divider
@@ -93,10 +91,7 @@ public class AppsDividerView extends View implements FloatingHeaderRow {
? R.color.all_apps_label_text_dark
: R.color.all_apps_label_text);
OnboardingPrefs<?> onboardingPrefs = ActivityContext.lookupContext(
getContext()).getOnboardingPrefs();
mShowAllAppsLabel = onboardingPrefs == null || !onboardingPrefs.hasReachedMaxCount(
ALL_APPS_VISITED_COUNT);
mShowAllAppsLabel = !ALL_APPS_VISITED_COUNT.hasReachedMax(context);
}
public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) {
@@ -36,6 +36,7 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.anim.AlphaUpdateListener;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusIndicatorHelper;
import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
import com.android.launcher3.model.data.ItemInfo;
@@ -126,6 +127,10 @@ public class PredictionRowView<T extends Context & ActivityContext>
int verticalPadding = getResources().getDimensionPixelSize(
R.dimen.all_apps_predicted_icon_vertical_padding);
int totalHeight = iconHeight + iconPadding + textHeight + verticalPadding * 2;
if (FeatureFlags.enableTwolineAllapps()) {
// Add extra textHeight to the existing total height.
totalHeight += textHeight;
}
return getVisibility() == GONE ? 0 : totalHeight + getPaddingTop() + getPaddingBottom();
}
@@ -0,0 +1,109 @@
/*
* 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.desktop
import android.app.IApplicationThread
import android.os.IBinder
import android.os.RemoteException
import android.util.Log
import android.view.SurfaceControl
import android.window.IRemoteTransition
import android.window.IRemoteTransitionFinishedCallback
import android.window.RemoteTransition
import android.window.TransitionInfo
import com.android.launcher3.statehandlers.DepthController
import com.android.launcher3.statemanager.StateManager
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.quickstep.SystemUiProxy
import com.android.quickstep.TaskViewUtils
import com.android.quickstep.views.DesktopTaskView
import java.util.function.Consumer
/** Manage recents related operations with desktop tasks */
class DesktopRecentsTransitionController(
private val stateManager: StateManager<*>,
private val systemUiProxy: SystemUiProxy,
private val appThread: IApplicationThread,
private val depthController: DepthController?
) {
/** Launch desktop tasks from recents view */
fun launchDesktopFromRecents(
desktopTaskView: DesktopTaskView,
callback: Consumer<Boolean>? = null
) {
val animRunner =
RemoteDesktopLaunchTransitionRunner(
desktopTaskView,
stateManager,
depthController,
callback
)
val transition = RemoteTransition(animRunner, appThread, "RecentsToDesktop")
systemUiProxy.showDesktopApps(desktopTaskView.display.displayId, transition)
}
private class RemoteDesktopLaunchTransitionRunner(
private val desktopTaskView: DesktopTaskView,
private val stateManager: StateManager<*>,
private val depthController: DepthController?,
private val successCallback: Consumer<Boolean>?
) : IRemoteTransition.Stub() {
override fun startAnimation(
token: IBinder,
info: TransitionInfo,
t: SurfaceControl.Transaction,
finishCallback: IRemoteTransitionFinishedCallback
) {
val errorHandlingFinishCallback = Runnable {
try {
finishCallback.onTransitionFinished(null /* wct */, null /* sct */)
} catch (e: RemoteException) {
Log.e(TAG, "Failed to call finish callback for desktop recents animation", e)
}
}
MAIN_EXECUTOR.execute {
TaskViewUtils.composeRecentsDesktopLaunchAnimator(
desktopTaskView,
stateManager,
depthController,
info,
t
) {
errorHandlingFinishCallback.run()
successCallback?.accept(true)
}
}
}
override fun mergeAnimation(
transition: IBinder,
info: TransitionInfo,
t: SurfaceControl.Transaction,
mergeTarget: IBinder,
finishCallback: IRemoteTransitionFinishedCallback
) {}
override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {
}
}
companion object {
const val TAG = "DesktopRecentsTransitionController"
}
}
@@ -23,6 +23,7 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH
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 static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -41,6 +42,7 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.Hotseat;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimationSuccessListener;
@@ -59,7 +61,6 @@ import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.uioverrides.PredictedAppIcon;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.views.Snackbar;
import java.io.PrintWriter;
@@ -104,12 +105,11 @@ public class HotseatPredictionController implements DragController.DragListener,
if (mLauncher.getWorkspace().isSwitchingState()) return false;
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onWorkspaceItemLongClick");
if (mEnableHotseatLongPressTipForTesting && !mLauncher.getOnboardingPrefs().getBoolean(
OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN)) {
if (mEnableHotseatLongPressTipForTesting && !HOTSEAT_LONGPRESS_TIP_SEEN.get(mLauncher)) {
Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
R.string.hotseat_prediction_settings, null,
() -> mLauncher.startActivity(getSettingsIntent()));
mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN);
LauncherPrefs.get(mLauncher).put(HOTSEAT_LONGPRESS_TIP_SEEN, true);
mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
return true;
}
@@ -67,6 +67,9 @@ public final class PredictionHelper {
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
return new AppTarget.Builder(new AppTargetId("folder:" + info.id),
context.getPackageName(), info.user).build();
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR) {
return new AppTarget.Builder(new AppTargetId("app_pair:" + info.id),
context.getPackageName(), info.user).build();
}
return null;
}
@@ -15,8 +15,9 @@
*/
package com.android.launcher3.model;
import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
import static com.android.launcher3.EncryptionType.ENCRYPTED;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.model.QuickstepModelDelegate.LAST_PREDICTION_ENABLED_STATE;
import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
import android.app.prediction.AppTarget;
@@ -29,6 +30,7 @@ import android.os.UserHandle;
import androidx.annotation.NonNull;
import com.android.launcher3.ConstantItem;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
@@ -47,6 +49,9 @@ import java.util.stream.Collectors;
*/
public class PredictionUpdateTask extends BaseModelUpdateTask {
public static final ConstantItem<Boolean> LAST_PREDICTION_ENABLED =
nonRestorableItem("last_prediction_enabled_state", true, ENCRYPTED);
private final List<AppTarget> mTargets;
private final PredictorState mPredictorState;
@@ -61,8 +66,7 @@ public class PredictionUpdateTask extends BaseModelUpdateTask {
Context context = app.getContext();
// TODO: remove this
LauncherPrefs.getDevicePrefs(context).edit()
.putBoolean(LAST_PREDICTION_ENABLED_STATE, !mTargets.isEmpty()).apply();
LauncherPrefs.get(context).put(LAST_PREDICTION_ENABLED, !mTargets.isEmpty());
Set<UserHandle> usersForChangedShortcuts =
dataModel.extraItems.get(mPredictorState.containerId).items.stream()
@@ -18,7 +18,8 @@ package com.android.launcher3.model;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.formatElapsedTime;
import static com.android.launcher3.LauncherPrefs.getDevicePrefs;
import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
import static com.android.launcher3.EncryptionType.ENCRYPTED;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
@@ -39,7 +40,6 @@ import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
@@ -55,8 +55,10 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import com.android.launcher3.ConstantItem;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.InstanceId;
@@ -86,14 +88,15 @@ import java.util.stream.IntStream;
*/
public class QuickstepModelDelegate extends ModelDelegate {
public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS";
private static final String BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets";
private static final int NUM_OF_RECOMMENDED_WIDGETS_PREDICATION = 20;
private static final boolean IS_DEBUG = false;
private static final String TAG = "QuickstepModelDelegate";
private static final ConstantItem<Long> LAST_SNAPSHOT_TIME_MILLIS =
nonRestorableItem("LAST_SNAPSHOT_TIME_MILLIS", 0L, ENCRYPTED);
@VisibleForTesting
final PredictorState mAllAppsState =
new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
@@ -211,8 +214,8 @@ public class QuickstepModelDelegate extends ModelDelegate {
super.modelLoadComplete();
// Log snapshot of the model
SharedPreferences prefs = getDevicePrefs(mApp.getContext());
long lastSnapshotTimeMillis = prefs.getLong(LAST_SNAPSHOT_TIME_MILLIS, 0);
LauncherPrefs prefs = LauncherPrefs.get(mApp.getContext());
long lastSnapshotTimeMillis = prefs.get(LAST_SNAPSHOT_TIME_MILLIS);
// Log snapshot only if previous snapshot was older than a day
long now = System.currentTimeMillis();
if (now - lastSnapshotTimeMillis < DAY_IN_MILLIS) {
@@ -233,7 +236,7 @@ public class QuickstepModelDelegate extends ModelDelegate {
StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId);
}
additionalSnapshotEvents(instanceId);
prefs.edit().putLong(LAST_SNAPSHOT_TIME_MILLIS, now).apply();
prefs.put(LAST_SNAPSHOT_TIME_MILLIS, now);
}
// Only register for launcher snapshot logging if this is the primary ModelDelegate
@@ -22,7 +22,6 @@ import android.content.Context;
import com.android.launcher3.appprediction.AppsDividerView;
import com.android.launcher3.appprediction.PredictionRowView;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.views.ActivityContext;
/**
@@ -30,22 +29,21 @@ import com.android.launcher3.views.ActivityContext;
*/
@SuppressWarnings("unused")
public final class SecondaryDisplayPredictionsImpl extends SecondaryDisplayPredictions {
private final ActivityContext mActivityContext;
private final Context mContext;
public SecondaryDisplayPredictionsImpl(Context context) {
mContext = context;
mActivityContext = ActivityContext.lookupContext(context);
}
@Override
void updateAppDivider() {
OnboardingPrefs<?> onboardingPrefs = mActivityContext.getOnboardingPrefs();
if (onboardingPrefs != null) {
mActivityContext.getAppsView().getFloatingHeaderView()
.findFixedRowByType(AppsDividerView.class)
.setShowAllAppsLabel(
!onboardingPrefs.hasReachedMaxCount(ALL_APPS_VISITED_COUNT));
onboardingPrefs.incrementEventCount(ALL_APPS_VISITED_COUNT);
}
mActivityContext.getAppsView().getFloatingHeaderView()
.findFixedRowByType(AppsDividerView.class)
.setShowAllAppsLabel(!ALL_APPS_VISITED_COUNT.hasReachedMax(mContext));
ALL_APPS_VISITED_COUNT.increment(mContext);
}
@Override
@@ -15,8 +15,11 @@
*/
package com.android.launcher3.statehandlers;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import android.os.Debug;
import android.os.SystemProperties;
import android.util.Log;
import android.view.View;
@@ -46,6 +49,7 @@ public class DesktopVisibilityController {
private boolean mFreeformTasksVisible;
private boolean mInOverviewState;
private boolean mBackgroundStateEnabled;
private boolean mGestureInProgress;
@Nullable
@@ -102,19 +106,15 @@ public class DesktopVisibilityController {
SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(null);
}
/**
* Whether desktop mode is supported.
*/
private boolean isDesktopModeSupported() {
return SystemProperties.getBoolean("persist.wm.debug.desktop_mode", false)
|| SystemProperties.getBoolean("persist.wm.debug.desktop_mode_2", false);
}
/**
* Whether freeform windows are visible in desktop mode.
*/
public boolean areFreeformTasksVisible() {
return mFreeformTasksVisible;
if (DEBUG) {
Log.d(TAG, "areFreeformTasksVisible: freeformVisible=" + mFreeformTasksVisible
+ " overview=" + mInOverviewState);
}
return mFreeformTasksVisible && !mInOverviewState;
}
/**
@@ -122,7 +122,8 @@ public class DesktopVisibilityController {
*/
public void setFreeformTasksVisible(boolean freeformTasksVisible) {
if (DEBUG) {
Log.d(TAG, "setFreeformTasksVisible: visible=" + freeformTasksVisible);
Log.d(TAG, "setFreeformTasksVisible: visible=" + freeformTasksVisible
+ " currentValue=" + mFreeformTasksVisible);
}
if (!isDesktopModeSupported()) {
return;
@@ -147,11 +148,21 @@ public class DesktopVisibilityController {
}
/**
* Sets whether the overview is visible and updates launcher visibility based on that.
* Process launcher state change and update launcher view visibility based on desktop state
*/
public void setOverviewStateEnabled(boolean overviewStateEnabled) {
public void onLauncherStateChanged(LauncherState state) {
if (DEBUG) {
Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled);
Log.d(TAG, "onLauncherStateChanged: newState=" + state);
}
setBackgroundStateEnabled(state == BACKGROUND_APP);
// Desktop visibility tracks overview and background state separately
setOverviewStateEnabled(state != BACKGROUND_APP && state.overviewUi);
}
private void setOverviewStateEnabled(boolean overviewStateEnabled) {
if (DEBUG) {
Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled
+ " currentValue=" + mInOverviewState);
}
if (!isDesktopModeSupported()) {
return;
@@ -161,7 +172,30 @@ public class DesktopVisibilityController {
if (mInOverviewState) {
setLauncherViewsVisibility(View.VISIBLE);
markLauncherResumed();
} else if (mFreeformTasksVisible) {
} else if (areFreeformTasksVisible() && !mGestureInProgress) {
// Switching out of overview state and gesture finished.
// If freeform tasks are still visible, hide launcher again.
setLauncherViewsVisibility(View.INVISIBLE);
markLauncherPaused();
}
}
}
private void setBackgroundStateEnabled(boolean backgroundStateEnabled) {
if (DEBUG) {
Log.d(TAG, "setBackgroundStateEnabled: enabled=" + backgroundStateEnabled
+ " currentValue=" + mBackgroundStateEnabled);
}
if (!isDesktopModeSupported()) {
return;
}
if (backgroundStateEnabled != mBackgroundStateEnabled) {
mBackgroundStateEnabled = backgroundStateEnabled;
if (mBackgroundStateEnabled) {
setLauncherViewsVisibility(View.VISIBLE);
markLauncherResumed();
} else if (areFreeformTasksVisible() && !mGestureInProgress) {
// Switching out of background state. If freeform tasks are visible, pause launcher.
setLauncherViewsVisibility(View.INVISIBLE);
markLauncherPaused();
}
@@ -182,6 +216,9 @@ public class DesktopVisibilityController {
if (!isDesktopModeSupported()) {
return;
}
if (DEBUG) {
Log.d(TAG, "setRecentsGestureStart");
}
setRecentsGestureInProgress(true);
}
@@ -193,6 +230,9 @@ public class DesktopVisibilityController {
if (!isDesktopModeSupported()) {
return;
}
if (DEBUG) {
Log.d(TAG, "setRecentsGestureEnd: endTarget=" + endTarget);
}
setRecentsGestureInProgress(false);
if (endTarget == null) {
@@ -202,9 +242,6 @@ public class DesktopVisibilityController {
}
private void setRecentsGestureInProgress(boolean gestureInProgress) {
if (DEBUG) {
Log.d(TAG, "setGestureInProgress: inProgress=" + gestureInProgress);
}
if (gestureInProgress != mGestureInProgress) {
mGestureInProgress = gestureInProgress;
}
@@ -221,7 +258,8 @@ public class DesktopVisibilityController {
private void setLauncherViewsVisibility(int visibility) {
if (DEBUG) {
Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility);
Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility + " "
+ Debug.getCaller());
}
View workspaceView = mLauncher.getWorkspace();
if (workspaceView != null) {
@@ -235,7 +273,7 @@ public class DesktopVisibilityController {
private void markLauncherPaused() {
if (DEBUG) {
Log.d(TAG, "markLauncherPaused");
Log.d(TAG, "markLauncherPaused " + Debug.getCaller());
}
StatefulActivity<LauncherState> activity =
QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
@@ -246,7 +284,7 @@ public class DesktopVisibilityController {
private void markLauncherResumed() {
if (DEBUG) {
Log.d(TAG, "markLauncherResumed");
Log.d(TAG, "markLauncherResumed " + Debug.getCaller());
}
StatefulActivity<LauncherState> activity =
QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
@@ -20,8 +20,6 @@ import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
@@ -34,12 +32,10 @@ public abstract class BaseTaskbarContext extends ContextThemeWrapper implements
protected final LayoutInflater mLayoutInflater;
private final List<OnDeviceProfileChangeListener> mDPChangeListeners = new ArrayList<>();
private final OnboardingPrefs<BaseTaskbarContext> mOnboardingPrefs;
public BaseTaskbarContext(Context windowContext) {
super(windowContext, Themes.getActivityThemeRes(windowContext));
mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
mOnboardingPrefs = new OnboardingPrefs<>(this, LauncherPrefs.getPrefs(this));
}
@Override
@@ -52,11 +48,6 @@ public abstract class BaseTaskbarContext extends ContextThemeWrapper implements
return mDPChangeListeners;
}
@Override
public OnboardingPrefs<BaseTaskbarContext> getOnboardingPrefs() {
return mOnboardingPrefs;
}
/** Callback invoked when a drag is initiated within this context. */
public abstract void onDragStart();
@@ -18,11 +18,15 @@ package com.android.launcher3.taskbar;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_NOTIFICATIONS;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_QUICK_SETTINGS;
import android.content.Context;
import android.content.pm.ActivityInfo.Config;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import com.android.launcher3.R;
/**
@@ -39,8 +43,8 @@ public class DesktopNavbarButtonsViewController extends NavbarButtonsViewControl
private TaskbarControllers mControllers;
public DesktopNavbarButtonsViewController(TaskbarActivityContext context,
FrameLayout navButtonsView) {
super(context, navButtonsView);
@Nullable Context navigationBarPanelContext, FrameLayout navButtonsView) {
super(context, navigationBarPanelContext, navButtonsView);
mContext = context;
mNavButtonsView = navButtonsView;
mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
@@ -56,6 +60,11 @@ public class DesktopNavbarButtonsViewController extends NavbarButtonsViewControl
@Override
public void init(TaskbarControllers controllers) {
mControllers = controllers;
super.init(controllers);
}
@Override
protected void setupController() {
mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarHeight;
// Quick settings and notifications buttons
@@ -72,4 +81,7 @@ public class DesktopNavbarButtonsViewController extends NavbarButtonsViewControl
/** Cleans up on destroy */
@Override
public void onDestroy() { }
@Override
public void onConfigurationChanged(@Config int configChanges) { }
}
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.taskbar;
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
@@ -28,7 +30,6 @@ 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;
@@ -109,7 +110,7 @@ public final class KeyboardQuickSwitchController implements
DesktopVisibilityController desktopController =
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
final boolean onDesktop =
DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED
isDesktopModeSupported()
&& desktopController != null
&& desktopController.areFreeformTasksVisible();
@@ -136,7 +137,7 @@ public final class KeyboardQuickSwitchController implements
// Hide all desktop tasks and show them on the hidden tile
int hiddenDesktopTasks = 0;
if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
if (isDesktopModeSupported()) {
DesktopTask desktopTask = findDesktopTask(tasks);
if (desktopTask != null) {
hiddenDesktopTasks = desktopTask.tasks.size();
@@ -40,6 +40,8 @@ import com.android.systemui.shared.recents.model.ThumbnailData;
import java.util.function.Consumer;
import kotlin.Unit;
/**
* A view that displays a recent task during a keyboard quick switch.
*/
@@ -96,17 +98,18 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
Resources resources = mContext.getResources();
Preconditions.assertNotNull(mContent);
mBorderAnimator = new BorderAnimator(
mBorderAnimator = BorderAnimator.createScalingBorderAnimator(
/* borderRadiusPx= */ resources.getDimensionPixelSize(
R.dimen.keyboard_quick_switch_task_view_radius),
/* borderColor= */ mBorderColor,
/* borderAnimationParams= */ new BorderAnimator.ScalingParams(
/* borderWidthPx= */ resources.getDimensionPixelSize(
/* borderWidthPx= */ resources.getDimensionPixelSize(
R.dimen.keyboard_quick_switch_border_width),
/* boundsBuilder= */ bounds -> bounds.set(
0, 0, getWidth(), getHeight()),
/* targetView= */ this,
/* contentView= */ mContent));
/* boundsBuilder= */ bounds -> {
bounds.set(0, 0, getWidth(), getHeight());
return Unit.INSTANCE;
},
/* targetView= */ this,
/* contentView= */ mContent,
/* borderColor= */ mBorderColor);
}
@Nullable
@@ -46,6 +46,8 @@ 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.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.quickstep.util.GroupTask;
import java.util.HashMap;
@@ -360,11 +362,8 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
OPEN_OUTLINE_INTERPOLATOR));
}
});
if (currentFocusIndexOverride == -1) {
initializeScroll(/* index= */ 0, /* shouldTruncateTarget= */ false);
} else {
animateFocusMove(-1, currentFocusIndexOverride);
}
animateFocusMove(-1, currentFocusIndexOverride == -1
? Math.min(mContent.getChildCount(), 1) : currentFocusIndexOverride);
displayedContent.setVisibility(VISIBLE);
setVisibility(VISIBLE);
requestFocus();
@@ -413,7 +412,8 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
// there are more tasks
initializeScroll(
firstVisibleTaskIndex,
/* shouldTruncateTarget= */ firstVisibleTaskIndex != toIndex);
/* shouldTruncateTarget= */ firstVisibleTaskIndex != 0
&& firstVisibleTaskIndex != toIndex);
} else if (toIndex > fromIndex || toIndex == 0) {
// Scrolling to next task view
if (mIsRtl) {
@@ -438,6 +438,13 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
focusAnimation.start();
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
TestLogging.recordKeyEvent(
TestProtocol.SEQUENCE_MAIN, "KeyboardQuickSwitchView key event", event);
return super.dispatchKeyEvent(event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return (mViewCallbacks != null
@@ -454,56 +461,80 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
return;
}
if (mIsRtl) {
scrollRightTo(
task, shouldTruncateTarget, /* smoothScroll= */ false);
} else {
scrollLeftTo(
task, shouldTruncateTarget, /* smoothScroll= */ false);
task,
shouldTruncateTarget,
/* smoothScroll= */ false,
/* waitForLayout= */ true);
} else {
scrollRightTo(
task,
shouldTruncateTarget,
/* smoothScroll= */ false,
/* waitForLayout= */ true);
}
}
private void scrollRightTo(@NonNull View targetTask) {
scrollRightTo(targetTask, /* shouldTruncateTarget= */ false, /* smoothScroll= */ true);
scrollRightTo(
targetTask,
/* shouldTruncateTarget= */ false,
/* smoothScroll= */ true,
/* waitForLayout= */ false);
}
private void scrollRightTo(
@NonNull View targetTask, boolean shouldTruncateTarget, boolean smoothScroll) {
@NonNull View targetTask,
boolean shouldTruncateTarget,
boolean smoothScroll,
boolean waitForLayout) {
if (!mDisplayingRecentTasks) {
return;
}
if (smoothScroll && !shouldScroll(targetTask, shouldTruncateTarget)) {
return;
}
int scrollTo = targetTask.getLeft() - mSpacing
+ (shouldTruncateTarget ? targetTask.getWidth() / 2 : 0);
// Scroll so that the focused task is to the left of the list
if (smoothScroll) {
mScrollView.smoothScrollTo(scrollTo, 0);
} else {
mScrollView.scrollTo(scrollTo, 0);
}
runScrollCommand(waitForLayout, () -> {
int scrollTo = targetTask.getLeft() - mSpacing
+ (shouldTruncateTarget ? targetTask.getWidth() / 2 : 0);
// Scroll so that the focused task is to the left of the list
if (smoothScroll) {
mScrollView.smoothScrollTo(scrollTo, 0);
} else {
mScrollView.scrollTo(scrollTo, 0);
}
});
}
private void scrollLeftTo(@NonNull View targetTask) {
scrollLeftTo(targetTask, /* shouldTruncateTarget= */ false, /* smoothScroll= */ true);
scrollLeftTo(
targetTask,
/* shouldTruncateTarget= */ false,
/* smoothScroll= */ true,
/* waitForLayout= */ false);
}
private void scrollLeftTo(
@NonNull View targetTask, boolean shouldTruncateTarget, boolean smoothScroll) {
@NonNull View targetTask,
boolean shouldTruncateTarget,
boolean smoothScroll,
boolean waitForLayout) {
if (!mDisplayingRecentTasks) {
return;
}
if (smoothScroll && !shouldScroll(targetTask, shouldTruncateTarget)) {
return;
}
int scrollTo = targetTask.getRight() + mSpacing - mScrollView.getWidth()
- (shouldTruncateTarget ? targetTask.getWidth() / 2 : 0);
// Scroll so that the focused task is to the right of the list
if (smoothScroll) {
mScrollView.smoothScrollTo(scrollTo, 0);
} else {
mScrollView.scrollTo(scrollTo, 0);
}
runScrollCommand(waitForLayout, () -> {
int scrollTo = targetTask.getRight() + mSpacing - mScrollView.getWidth()
- (shouldTruncateTarget ? targetTask.getWidth() / 2 : 0);
// Scroll so that the focused task is to the right of the list
if (smoothScroll) {
mScrollView.smoothScrollTo(scrollTo, 0);
} else {
mScrollView.scrollTo(scrollTo, 0);
}
});
}
private boolean shouldScroll(@NonNull View targetTask, boolean shouldTruncateTarget) {
@@ -514,6 +545,21 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
return isTargetTruncated && !shouldTruncateTarget;
}
private void runScrollCommand(boolean waitForLayout, @NonNull Runnable scrollCommand) {
if (!waitForLayout) {
scrollCommand.run();
return;
}
mScrollView.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
scrollCommand.run();
mScrollView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
@Nullable
protected KeyboardQuickSwitchTaskView getTaskAt(int index) {
return !mDisplayingRecentTasks || index < 0 || index >= mContent.getChildCount()
@@ -24,7 +24,7 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
import com.android.quickstep.SystemUiProxy;
@@ -92,27 +92,19 @@ public class KeyboardQuickSwitchViewController {
protected void closeQuickSwitchView(boolean animate) {
if (mCloseAnimation != null) {
if (animate) {
// Let currently-running animation finish.
return;
} else {
mCloseAnimation.cancel();
// Let currently-running animation finish.
if (!animate) {
mCloseAnimation.end();
}
return;
}
if (!animate) {
mCloseAnimation = null;
onCloseComplete();
return;
}
mCloseAnimation = mKeyboardQuickSwitchView.getCloseAnimation();
mCloseAnimation.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
mCloseAnimation = null;
onCloseComplete();
}
});
mCloseAnimation.addListener(AnimatorListeners.forEndCallback(this::onCloseComplete));
mCloseAnimation.start();
}
@@ -141,11 +133,20 @@ public class KeyboardQuickSwitchViewController {
GroupTask task = mControllerCallbacks.getTaskAt(index);
if (task == null) {
return Math.max(0, index);
} else if (mOnDesktop) {
}
Task task2 = task.task2;
int runningTaskId = ActivityManagerWrapper.getInstance().getRunningTask().taskId;
if (runningTaskId == task.task1.key.id
|| (task2 != null && runningTaskId == task2.key.id)) {
// Ignore attempts to run the selected task if it is already running.
return -1;
}
if (mOnDesktop) {
UI_HELPER_EXECUTOR.execute(() ->
SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
.showDesktopApp(task.task1.key.id));
} else if (task.task2 == null) {
} else if (task2 == null) {
UI_HELPER_EXECUTOR.execute(() ->
ActivityManagerWrapper.getInstance().startActivityFromRecents(
task.task1.key,
@@ -153,13 +154,13 @@ public class KeyboardQuickSwitchViewController {
taskView == null ? mKeyboardQuickSwitchView : taskView, null)
.options));
} else {
mControllers.uiController.launchSplitTasks(
taskView == null ? mKeyboardQuickSwitchView : taskView, task);
mControllers.uiController.launchSplitTasks(task);
}
return -1;
}
private void onCloseComplete() {
mCloseAnimation = null;
mOverlayContext.getDragLayer().removeView(mKeyboardQuickSwitchView);
mControllerCallbacks.onCloseComplete();
}
@@ -18,7 +18,7 @@ package com.android.launcher3.taskbar;
import static com.android.launcher3.QuickstepTransitionManager.TRANSIENT_TASKBAR_TRANSITION_DURATION;
import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES;
import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_RESUMED;
import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_VISIBLE;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import android.animation.Animator;
@@ -26,7 +26,6 @@ import android.animation.AnimatorSet;
import android.os.RemoteException;
import android.util.Log;
import android.view.TaskTransitionSpec;
import android.view.View;
import android.view.WindowManagerGlobal;
import androidx.annotation.NonNull;
@@ -41,6 +40,7 @@ import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory;
@@ -99,7 +99,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
mLauncher.setTaskbarUIController(this);
onLauncherResumedOrPaused(mLauncher.hasBeenResumed(), true /* fromInit */);
onLauncherVisibilityChanged(mLauncher.hasBeenResumed(), true /* fromInit */);
onStashedInAppChanged(mLauncher.getDeviceProfile());
mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
@@ -118,7 +118,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
@Override
protected void onDestroy() {
super.onDestroy();
onLauncherResumedOrPaused(false);
onLauncherVisibilityChanged(false);
mTaskbarLauncherStateController.onDestroy();
mLauncher.setTaskbarUIController(null);
@@ -159,8 +159,9 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
* sub-animations are properly coordinated. This duration should not
* actually be used since this animation tracks a swipe progress.
*/
protected void addLauncherResumeAnimation(AnimatorSet animation, int placeholderDuration) {
animation.play(onLauncherResumedOrPaused(
protected void addLauncherVisibilityChangedAnimation(AnimatorSet animation,
int placeholderDuration) {
animation.play(onLauncherVisibilityChanged(
/* isResumed= */ true,
/* fromInit= */ false,
/* startAnimation= */ false,
@@ -170,40 +171,53 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
/**
* Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
*/
public void onLauncherResumedOrPaused(boolean isResumed) {
onLauncherResumedOrPaused(isResumed, false /* fromInit */);
public void onLauncherVisibilityChanged(boolean isVisible) {
onLauncherVisibilityChanged(isVisible, false /* fromInit */);
}
private void onLauncherResumedOrPaused(boolean isResumed, boolean fromInit) {
onLauncherResumedOrPaused(
isResumed,
private void onLauncherVisibilityChanged(boolean isVisible, boolean fromInit) {
onLauncherVisibilityChanged(
isVisible,
fromInit,
/* startAnimation= */ true,
DisplayController.isTransientTaskbar(mLauncher)
? TRANSIENT_TASKBAR_TRANSITION_DURATION
: (!isResumed
: (!isVisible
? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION
: QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION));
}
@Nullable
private Animator onLauncherResumedOrPaused(
boolean isResumed, boolean fromInit, boolean startAnimation, int duration) {
private Animator onLauncherVisibilityChanged(
boolean isVisible, boolean fromInit, boolean startAnimation, int duration) {
// Launcher is resumed during the swipe-to-overview gesture under shell-transitions, so
// avoid updating taskbar state in that situation (when it's non-interactive -- or
// "background") to avoid premature animations.
if (ENABLE_SHELL_TRANSITIONS && isResumed
if (ENABLE_SHELL_TRANSITIONS && isVisible
&& mLauncher.getStateManager().getState().hasFlag(FLAG_NON_INTERACTIVE)
&& !mLauncher.getStateManager().getState().isTaskbarAlignedWithHotseat(mLauncher)) {
return null;
}
mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, isResumed);
mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, isVisible);
return mTaskbarLauncherStateController.applyState(fromInit ? 0 : duration, startAnimation);
}
@Override
public void onStateTransitionCompletedAfterSwipeToHome(LauncherState state) {
mTaskbarLauncherStateController.onStateTransitionCompletedAfterSwipeToHome(state);
}
@Override
public void refreshResumedState() {
onLauncherResumedOrPaused(mLauncher.hasBeenResumed());
onLauncherVisibilityChanged(mLauncher.hasBeenResumed());
}
@Override
public void adjustHotseatForBubbleBar(boolean isBubbleBarVisible) {
if (mLauncher.getHotseat() != null) {
mLauncher.getHotseat().adjustForBubbleBar(isBubbleBarVisible);
}
}
/**
@@ -279,8 +293,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
// Persistent features EDU tooltip.
if (!DisplayController.isTransientTaskbar(mLauncher)) {
return !mLauncher.getOnboardingPrefs().hasReachedMaxCount(
OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP);
return !OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP.hasReachedMax(mLauncher);
}
// Transient swipe EDU tooltip.
@@ -327,10 +340,25 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
return mTaskbarInAppDisplayProgress.value > 0;
}
public boolean isBubbleBarEnabled() {
return BubbleBarController.isBubbleBarEnabled();
}
/** Whether the bubble bar has any bubbles. */
public boolean hasBubbles() {
if (mControllers == null) {
return false;
}
if (mControllers.bubbleControllers.isEmpty()) {
return false;
}
return mControllers.bubbleControllers.get().bubbleBarViewController.hasBubbles();
}
@Override
public void onExpandPip() {
super.onExpandPip();
mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, false);
mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, false);
mTaskbarLauncherStateController.applyState();
}
@@ -362,8 +390,8 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
}
@Override
public void launchSplitTasks(@NonNull View taskView, @NonNull GroupTask groupTask) {
mLauncher.launchSplitTasks(taskView, groupTask);
public void launchSplitTasks(@NonNull GroupTask groupTask) {
mLauncher.launchSplitTasks(groupTask);
}
@Override
@@ -23,8 +23,10 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static com.android.launcher3.LauncherAnimUtils.ROTATION_DRAWABLE_PERCENT;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.SYSUI_SURFACE_PROGRESS_INDEX;
import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
@@ -51,6 +53,7 @@ import android.animation.ObjectAnimator;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
import android.annotation.LayoutRes;
import android.content.Context;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -78,6 +81,8 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
@@ -88,10 +93,10 @@ 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;
import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.launcher3.views.BaseDragLayer;
import com.android.systemui.shared.rotation.FloatingRotationButton;
import com.android.systemui.shared.rotation.RotationButton;
@@ -144,6 +149,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
private int mState;
private final TaskbarActivityContext mContext;
private final @Nullable Context mNavigationBarPanelContext;
private final WindowManagerProxy mWindowManagerProxy;
private final FrameLayout mNavButtonsView;
private final LinearLayout mNavButtonContainer;
// Used for IME+A11Y buttons
@@ -190,6 +197,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
private MultiValueAlpha mBackButtonAlpha;
private MultiValueAlpha mHomeButtonAlpha;
private FloatingRotationButton mFloatingRotationButton;
private ImageView mImeSwitcherButton;
// Variables for moving nav buttons to a separate window above IME
private boolean mAreNavButtonsInSeparateWindow = false;
@@ -198,10 +206,12 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
this::onComputeInsetsForSeparateWindow;
private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
private ImageView mRecentsButton;
private DisplayController mDisplayController;
public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
public NavbarButtonsViewController(TaskbarActivityContext context,
@Nullable Context navigationBarPanelContext, FrameLayout navButtonsView) {
mContext = context;
mNavigationBarPanelContext = navigationBarPanelContext;
mWindowManagerProxy = WindowManagerProxy.INSTANCE.get(mContext);
mNavButtonsView = navButtonsView;
mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons);
@@ -219,25 +229,27 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
*/
public void init(TaskbarControllers controllers) {
mControllers = controllers;
setupController();
}
protected void setupController() {
boolean isThreeButtonNav = mContext.isThreeButtonNav();
DeviceProfile deviceProfile = mContext.getDeviceProfile();
Resources resources = mContext.getResources();
Point p = !mContext.isUserSetupComplete()
? new Point(0, controllers.taskbarActivityContext.getSetupWindowHeight())
? new Point(0, mControllers.taskbarActivityContext.getSetupWindowHeight())
: DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources,
TaskbarManager.isPhoneMode(deviceProfile));
mNavButtonsView.getLayoutParams().height = p.y;
mDisplayController = DisplayController.INSTANCE.get(mContext);
mIsImeRenderingNavButtons =
InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
if (!mIsImeRenderingNavButtons) {
// IME switcher
View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
mImeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
mControllers.navButtonController, R.id.ime_switcher);
mPropertyHolders.add(new StatePropertyHolder(imeSwitcherButton,
mPropertyHolders.add(new StatePropertyHolder(mImeSwitcherButton,
flags -> ((flags & FLAG_SWITCHER_SHOWING) != 0)
&& ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)));
}
@@ -306,7 +318,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
rotationButton.hide();
mControllers.rotationButtonController.setRotationButton(rotationButton, null);
} else {
mFloatingRotationButton = new FloatingRotationButton(mContext,
mFloatingRotationButton = new FloatingRotationButton(
ENABLE_TASKBAR_NAVBAR_UNIFICATION ? mNavigationBarPanelContext : mContext,
R.string.accessibility_rotate_button,
R.layout.rotate_suggestion,
R.id.rotate_suggestion,
@@ -375,10 +388,12 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
int navButtonSize = mContext.getResources().getDimensionPixelSize(
R.dimen.taskbar_nav_buttons_size);
boolean isRtl = Utilities.isRtl(mContext.getResources());
mPropertyHolders.add(new StatePropertyHolder(
mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
|| (flags & FLAG_KEYGUARD_VISIBLE) != 0,
VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));
if (!isPhoneMode(mContext.getDeviceProfile())) {
mPropertyHolders.add(new StatePropertyHolder(
mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
|| (flags & FLAG_KEYGUARD_VISIBLE) != 0,
VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));
}
// home button
mHomeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
@@ -467,7 +482,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
/**
* @return {@code true} if A11y is showing in 3 button nav taskbar
*/
private boolean isContextualButtonShowing() {
private boolean isA11yButtonPersistent() {
return mContext.isThreeButtonNav() && (mState & FLAG_A11Y_VISIBLE) != 0;
}
@@ -730,12 +745,15 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
// TODO(b/244231596) we're getting the incorrect kidsMode value in small-screen
boolean isInKidsMode = mContext.isNavBarKidsModeActive();
if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
NavButtonLayoutter navButtonLayoutter =
NavButtonLayoutFactory.Companion.getUiLayoutter(
dp, mNavButtonsView, res, isInKidsMode, isInSetup, isThreeButtonNav,
TaskbarManager.isPhoneMode(dp), mDisplayController.getInfo().rotation);
navButtonLayoutter.layoutButtons(dp, isContextualButtonShowing());
dp, mNavButtonsView, mImeSwitcherButton,
mControllers.rotationButtonController.getRotationButton(),
mA11yButton, res, isInKidsMode, isInSetup, isThreeButtonNav,
TaskbarManager.isPhoneMode(dp),
mWindowManagerProxy.getRotation(mContext));
navButtonLayoutter.layoutButtons(dp, isA11yButtonPersistent());
updateNavButtonColor();
return;
}
@@ -831,7 +849,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
int contextualWidth = mEndContextualContainer.getWidth();
// 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) {
if (isA11yButtonPersistent() && navMarginEnd < contextualWidth) {
// Additional spacing, eat up half of space between last icon and nav button
navMarginEnd += res.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2;
}
@@ -108,7 +108,7 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
Resources resources = mActivity.getResources();
if (isPhoneGestureNavMode(mActivity.getDeviceProfile())) {
mTaskbarSize = resources.getDimensionPixelSize(R.dimen.taskbar_size);
mTaskbarSize = resources.getDimensionPixelSize(R.dimen.taskbar_phone_size);
mStashedHandleWidth =
resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen);
} else {
@@ -20,17 +20,22 @@ import static android.os.Trace.TRACE_TAG_APP;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY;
import static com.android.launcher3.Flags.enableCursorHoverStates;
import static com.android.launcher3.Utilities.calculateTextHeight;
import static com.android.launcher3.Utilities.isRunningInTestHarness;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING;
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_FULLSCREEN;
import static com.android.launcher3.taskbar.TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW;
import static com.android.launcher3.testing.shared.ResourceUtils.getBoolByName;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
@@ -69,9 +74,11 @@ import androidx.annotation.VisibleForTesting;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.folder.Folder;
@@ -101,6 +108,7 @@ import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.NavigationMode;
@@ -109,6 +117,7 @@ import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.ViewCache;
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.views.RecentsView;
@@ -121,7 +130,9 @@ import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
/**
* The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
@@ -136,6 +147,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
private static final String WINDOW_TITLE = "Taskbar";
private final @Nullable Context mNavigationBarPanelContext;
private final TaskbarDragLayer mDragLayer;
private final TaskbarControllers mControllers;
@@ -167,13 +180,20 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
private final TaskbarShortcutMenuAccessibilityDelegate mAccessibilityDelegate;
public TaskbarActivityContext(Context windowContext, DeviceProfile launcherDp,
private DeviceProfile mTransientTaskbarDeviceProfile;
private DeviceProfile mPersistentTaskbarDeviceProfile;
private final LauncherPrefs mLauncherPrefs;
public TaskbarActivityContext(Context windowContext,
@Nullable Context navigationBarPanelContext, DeviceProfile launcherDp,
TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
unfoldTransitionProgressProvider) {
super(windowContext);
mNavigationBarPanelContext = navigationBarPanelContext;
applyDeviceProfile(launcherDp);
final Resources resources = getResources();
mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
@@ -193,11 +213,16 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
Display display = windowContext.getDisplay();
Context c = getApplicationContext();
mWindowManager = c.getSystemService(WindowManager.class);
mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
boolean phoneMode = TaskbarManager.isPhoneMode(mDeviceProfile);
mLeftCorner = phoneMode
? null
: display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
mRightCorner = phoneMode
? null
: display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
// Inflate views.
boolean phoneMode = TaskbarManager.isPhoneMode(mDeviceProfile);
int taskbarLayout = DisplayController.isTransientTaskbar(this) && !phoneMode
? R.layout.transient_taskbar
: R.layout.taskbar;
@@ -215,7 +240,8 @@ 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 && bubbleBarView != null) {
BubbleBarController.onTaskbarRecreated();
if (BubbleBarController.isBubbleBarEnabled() && bubbleBarView != null) {
bubbleControllersOptional = Optional.of(new BubbleControllers(
new BubbleBarController(this, bubbleBarView),
new BubbleBarViewController(this, bubbleBarView),
@@ -240,16 +266,18 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
new TaskbarDragController(this),
buttonController,
isDesktopMode
? new DesktopNavbarButtonsViewController(this, navButtonsView)
: new NavbarButtonsViewController(this, navButtonsView),
? new DesktopNavbarButtonsViewController(this, mNavigationBarPanelContext,
navButtonsView)
: new NavbarButtonsViewController(this, mNavigationBarPanelContext,
navButtonsView),
rotationButtonController,
new TaskbarDragLayerController(this, mDragLayer),
new TaskbarViewController(this, taskbarView),
new TaskbarScrimViewController(this, taskbarScrimView),
new TaskbarUnfoldAnimationController(this, unfoldTransitionProgressProvider,
mWindowManager,
new RotationChangeProvider(c.getSystemService(DisplayManager.class), this,
getMainThreadHandler())),
mWindowManager,
new RotationChangeProvider(c.getSystemService(DisplayManager.class), this,
getMainThreadHandler())),
new TaskbarKeyguardController(this),
new StashedHandleViewController(this, stashedHandleView),
new TaskbarStashController(this),
@@ -267,8 +295,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
: TaskbarRecentAppsController.DEFAULT,
new TaskbarEduTooltipController(this),
new KeyboardQuickSwitchController(),
new TaskbarDividerPopupController(this),
new TaskbarPinningController(this),
bubbleControllersOptional);
mLauncherPrefs = LauncherPrefs.get(this);
}
/** Updates {@link DeviceProfile} instances for any Taskbar windows. */
@@ -288,20 +318,42 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
* 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;
Consumer<DeviceProfile> overrideProvider = 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();
// Update icon size
deviceProfile.iconSizePx = deviceProfile.taskbarIconSize;
deviceProfile.updateIconSize(1f, getResources());
};
mDeviceProfile = originDeviceProfile.toBuilder(this)
.withDimensionsOverride(overrideProvider).build();
if (DisplayController.isTransientTaskbar(this)) {
mTransientTaskbarDeviceProfile = mDeviceProfile;
mPersistentTaskbarDeviceProfile = mDeviceProfile
.toBuilder(this)
.withDimensionsOverride(overrideProvider)
.setIsTransientTaskbar(false)
.build();
} else {
mPersistentTaskbarDeviceProfile = mDeviceProfile;
mTransientTaskbarDeviceProfile = mDeviceProfile
.toBuilder(this)
.withDimensionsOverride(overrideProvider)
.setIsTransientTaskbar(true)
.build();
}
mNavMode = DisplayController.getNavigationMode(this);
}
/** Called when the visibility of the bubble bar changed. */
public void bubbleBarVisibilityChanged(boolean isVisible) {
mControllers.uiController.adjustHotseatForBubbleBar(isVisible);
mControllers.taskbarViewController.resetIconAlignmentController();
}
public void init(@NonNull TaskbarSharedState sharedState) {
mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, getResources(), false);
@@ -320,18 +372,18 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
sharedState.systemBarAttrsBehavior);
onNavButtonsDarkIntensityChanged(sharedState.navButtonsDarkIntensity);
if (FLAG_HIDE_NAVBAR_WINDOW) {
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
// W/ the flag not set this entire class gets re-created, which resets the value of
// mIsDestroyed. We re-use the class for small-screen, so we explicitly have to mark
// this class as non-destroyed
mIsDestroyed = false;
}
if (!mAddedWindow) {
if (!enableTaskbarNoRecreate() && !mAddedWindow) {
mWindowManager.addView(mDragLayer, mWindowLayoutParams);
mAddedWindow = true;
} else {
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
notifyUpdateLayoutParams();
}
}
@@ -347,6 +399,11 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
mControllers.taskbarAllAppsController.toggle();
}
/** Toggles Taskbar All Apps overlay with keyboard ready for search. */
public void toggleAllAppsSearch() {
mControllers.taskbarAllAppsController.toggleSearch();
}
@Override
public DeviceProfile getDeviceProfile() {
return mDeviceProfile;
@@ -359,6 +416,11 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
getDeviceProfile().toSmallString());
}
@NonNull
public LauncherPrefs getLauncherPrefs() {
return mLauncherPrefs;
}
/**
* Returns the View bounds of transient taskbar.
*/
@@ -374,7 +436,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
/**
* Creates LayoutParams for adding a view directly to WindowManager as a new window.
* @param type The window type to pass to the created WindowManager.LayoutParams.
*
* @param type The window type to pass to the created WindowManager.LayoutParams.
* @param title The window title to pass to the created WindowManager.LayoutParams.
*/
public WindowManager.LayoutParams createDefaultWindowLayoutParams(int type, String title) {
@@ -413,9 +476,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
* for taskbar showing as navigation bar
*/
private WindowManager.LayoutParams createAllWindowParams() {
final int windowType =
ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL;
WindowManager.LayoutParams windowLayoutParams =
createDefaultWindowLayoutParams(TYPE_NAVIGATION_BAR_PANEL,
TaskbarActivityContext.WINDOW_TITLE);
createDefaultWindowLayoutParams(windowType, TaskbarActivityContext.WINDOW_TITLE);
boolean isPhoneNavMode = TaskbarManager.isPhoneButtonNavMode(this);
if (!isPhoneNavMode) {
return windowLayoutParams;
@@ -428,7 +492,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
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,
createDefaultWindowLayoutParams(windowType,
TaskbarActivityContext.WINDOW_TITLE);
switch (rot) {
case Surface.ROTATION_0, Surface.ROTATION_180 -> {
@@ -678,7 +742,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
mIsDestroyed = true;
setUIController(TaskbarUIController.DEFAULT);
mControllers.onDestroy();
if (!FLAG_HIDE_NAVBAR_WINDOW) {
if (!enableTaskbarNoRecreate() && !ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
mWindowManager.removeViewImmediate(mDragLayer);
mAddedWindow = false;
}
@@ -777,7 +841,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
// Overlay AFVs are in a separate window and do not require Taskbar to be fullscreen.
if (!isDragInProgress
&& !AbstractFloatingView.hasOpenView(
this, TYPE_ALL & ~TYPE_TASKBAR_OVERLAY_PROXY)) {
this, TYPE_ALL & ~TYPE_TASKBAR_OVERLAY_PROXY)) {
// Reverts Taskbar window to its original size
setTaskbarWindowFullscreen(false);
}
@@ -810,7 +874,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
}
mWindowLayoutParams.height = height;
mControllers.taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
notifyUpdateLayoutParams();
}
/**
@@ -819,9 +883,9 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
public int getDefaultTaskbarWindowHeight() {
Resources resources = getResources();
if (FLAG_HIDE_NAVBAR_WINDOW && mDeviceProfile.isPhone) {
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION && mDeviceProfile.isPhone) {
return isThreeButtonNav() ?
resources.getDimensionPixelSize(R.dimen.taskbar_size) :
resources.getDimensionPixelSize(R.dimen.taskbar_phone_size) :
resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
}
@@ -829,20 +893,46 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
return getSetupWindowHeight();
}
if (DisplayController.isTransientTaskbar(this)) {
return mDeviceProfile.taskbarHeight
+ (2 * mDeviceProfile.taskbarBottomMargin)
+ resources.getDimensionPixelSize(R.dimen.transient_taskbar_shadow_blur);
boolean shouldTreatAsTransient = DisplayController.isTransientTaskbar(this)
|| (enableTaskbarPinning() && !isThreeButtonNav());
int extraHeightForTaskbarTooltips = enableCursorHoverStates()
? resources.getDimensionPixelSize(R.dimen.arrow_toast_arrow_height)
+ (resources.getDimensionPixelSize(R.dimen.taskbar_tooltip_vertical_padding) * 2)
+ calculateTextHeight(
resources.getDimensionPixelSize(R.dimen.arrow_toast_text_size))
: 0;
// Return transient taskbar window height when pinning feature is enabled, so taskbar view
// does not get cut off during pinning animation.
if (shouldTreatAsTransient) {
DeviceProfile transientTaskbarDp = mDeviceProfile.toBuilder(this)
.setIsTransientTaskbar(true).build();
return transientTaskbarDp.taskbarHeight
+ (2 * transientTaskbarDp.taskbarBottomMargin)
+ Math.max(extraHeightForTaskbarTooltips, resources.getDimensionPixelSize(
R.dimen.transient_taskbar_shadow_blur));
}
return mDeviceProfile.taskbarHeight
+ Math.max(getLeftCornerRadius(), getRightCornerRadius());
+ Math.max(getLeftCornerRadius(), getRightCornerRadius())
+ extraHeightForTaskbarTooltips;
}
public int getSetupWindowHeight() {
return getResources().getDimensionPixelSize(R.dimen.taskbar_suw_frame);
}
public DeviceProfile getTransientTaskbarDeviceProfile() {
return mTransientTaskbarDeviceProfile;
}
public DeviceProfile getPersistentTaskbarDeviceProfile() {
return mPersistentTaskbarDeviceProfile;
}
/**
* Either adds or removes {@link WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} on the taskbar
* window.
@@ -853,7 +943,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
} else {
mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE;
}
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
notifyUpdateLayoutParams();
}
/**
@@ -904,6 +994,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
}
protected void onTaskbarIconClicked(View view) {
TaskbarUIController taskbarUIController = mControllers.uiController;
RecentsView recents = taskbarUIController.getRecentsView();
boolean shouldCloseAllOpenViews = true;
Object tag = view.getTag();
if (tag instanceof Task) {
@@ -911,41 +1003,26 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
ActivityOptions.makeBasic());
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
} else if (tag instanceof FolderInfo) {
} else if (tag instanceof FolderInfo fi && fi.itemType == Favorites.ITEM_TYPE_FOLDER) {
// Tapping an expandable folder icon on Taskbar
shouldCloseAllOpenViews = false;
FolderIcon folderIcon = (FolderIcon) view;
Folder folder = folderIcon.getFolder();
folder.setOnFolderStateChangedListener(newState -> {
if (newState == Folder.STATE_OPEN) {
setTaskbarWindowFocusableForIme(true);
} else if (newState == Folder.STATE_CLOSED) {
// Defer by a frame to ensure we're no longer fullscreen and thus won't jump.
getDragLayer().post(() -> setTaskbarWindowFocusableForIme(false));
folder.setOnFolderStateChangedListener(null);
}
});
setTaskbarWindowFullscreen(true);
getDragLayer().post(() -> {
folder.animateOpen();
getStatsLogManager().logger().withItemInfo(folder.mInfo).log(LAUNCHER_FOLDER_OPEN);
folder.iterateOverItems((itemInfo, itemView) -> {
mControllers.taskbarViewController
.setClickAndLongClickListenersForIcon(itemView);
// To play haptic when dragging, like other Taskbar items do.
itemView.setHapticFeedbackEnabled(true);
return false;
});
});
expandFolder((FolderIcon) view);
} else if (tag instanceof FolderInfo fi && fi.itemType == Favorites.ITEM_TYPE_APP_PAIR) {
// Tapping an app pair icon on Taskbar
if (recents != null && recents.isSplitSelectionActive()) {
// TODO (b/274835596): Implement "can't split with this" bounce animation
Toast.makeText(this, "Unable to split with an app pair. Select another app.",
Toast.LENGTH_SHORT).show();
} else {
// Else launch the selected app pair
launchFromTaskbarPreservingSplitIfVisible(recents, view, fi.contents);
mControllers.uiController.onTaskbarIconLaunched(fi);
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
}
} else if (tag instanceof WorkspaceItemInfo) {
// Tapping a launchable icon on Taskbar
WorkspaceItemInfo info = (WorkspaceItemInfo) tag;
if (!info.isDisabled() || !ItemClickHandler.handleDisabledItemClicked(info, this)) {
TaskbarUIController taskbarUIController = mControllers.uiController;
RecentsView recents = taskbarUIController.getRecentsView();
if (recents != null && recents.isSplitSelectionActive()) {
// If we are selecting a second app for split, launch the split tasks
taskbarUIController.triggerSecondAppForSplit(info, info.intent, view);
@@ -973,7 +1050,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
getSystemService(LauncherApps.class)
.startShortcut(packageName, id, null, null, info.user);
} else {
launchFromTaskbarPreservingSplitIfVisible(recents, info);
launchFromTaskbarPreservingSplitIfVisible(
recents, view, Collections.singletonList(info));
}
} catch (NullPointerException
@@ -984,7 +1062,21 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
Log.e(TAG, "Unable to launch. tag=" + info + " intent=" + intent, e);
return;
}
}
// If the app was launched from a folder, stash the taskbar after it closes
Folder f = Folder.getOpen(this);
if (f != null && f.getInfo().id == info.container) {
f.addOnFolderStateChangedListener(new Folder.OnFolderStateChangedListener() {
@Override
public void onFolderStateChanged(int newState) {
if (newState == Folder.STATE_CLOSED) {
f.removeOnFolderStateChangedListener(this);
mControllers.taskbarStashController
.updateAndAnimateTransientTaskbar(true);
}
}
});
}
mControllers.uiController.onTaskbarIconLaunched(info);
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
@@ -992,14 +1084,13 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
} else if (tag instanceof AppInfo) {
// Tapping an item in AllApps
AppInfo info = (AppInfo) tag;
TaskbarUIController taskbarUIController = mControllers.uiController;
RecentsView recents = taskbarUIController.getRecentsView();
if (recents != null
&& taskbarUIController.getRecentsView().isSplitSelectionActive()) {
// If we are selecting a second app for split, launch the split tasks
taskbarUIController.triggerSecondAppForSplit(info, info.intent, view);
} else {
launchFromTaskbarPreservingSplitIfVisible(recents, info);
launchFromTaskbarPreservingSplitIfVisible(
recents, view, Collections.singletonList(info));
}
mControllers.uiController.onTaskbarIconLaunched(info);
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
@@ -1021,17 +1112,22 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
* (potentially breaking a split pair).
*/
private void launchFromTaskbarPreservingSplitIfVisible(@Nullable RecentsView recents,
ItemInfo info) {
@Nullable View launchingIconView, List<? extends ItemInfo> itemInfos) {
if (recents == null) {
return;
}
boolean findExactPairMatch = itemInfos.size() == 2;
// Convert the list of ItemInfo instances to a list of ComponentKeys
List<ComponentKey> componentKeys =
itemInfos.stream().map(ItemInfo::getComponentKey).toList();
recents.getSplitSelectController().findLastActiveTasksAndRunCallback(
Collections.singletonList(info.getComponentKey()),
componentKeys,
findExactPairMatch,
foundTasks -> {
@Nullable Task foundTask = foundTasks.get(0);
if (foundTask != null) {
TaskView foundTaskView =
recents.getTaskViewByTaskId(foundTask.key.id);
TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
if (foundTaskView != null
&& foundTaskView.isVisibleToUser()) {
TestLogging.recordEvent(
@@ -1040,8 +1136,16 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
return;
}
}
startItemInfoActivity(info);
});
if (findExactPairMatch) {
// We did not find the app pair we were looking for, so launch one.
recents.getSplitSelectController().getAppPairsController().launchAppPair(
(AppPairIcon) launchingIconView);
} else {
startItemInfoActivity(itemInfos.get(0));
}
}
);
}
private void startItemInfoActivity(ItemInfo info) {
@@ -1063,6 +1167,41 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
}
}
/** Expands a folder icon when it is clicked */
private void expandFolder(FolderIcon folderIcon) {
Folder folder = folderIcon.getFolder();
folder.setPriorityOnFolderStateChangedListener(
new Folder.OnFolderStateChangedListener() {
@Override
public void onFolderStateChanged(int newState) {
if (newState == Folder.STATE_OPEN) {
setTaskbarWindowFocusableForIme(true);
} else if (newState == Folder.STATE_CLOSED) {
// Defer by a frame to ensure we're no longer fullscreen and thus
// won't jump.
getDragLayer().post(() -> setTaskbarWindowFocusableForIme(false));
folder.setPriorityOnFolderStateChangedListener(null);
}
}
});
setTaskbarWindowFullscreen(true);
getDragLayer().post(() -> {
folder.animateOpen();
getStatsLogManager().logger().withItemInfo(folder.mInfo).log(LAUNCHER_FOLDER_OPEN);
folder.iterateOverItems((itemInfo, itemView) -> {
mControllers.taskbarViewController
.setClickAndLongClickListenersForIcon(itemView);
// To play haptic when dragging, like other Taskbar items do.
itemView.setHapticFeedbackEnabled(true);
return false;
});
});
}
/**
* Returns whether the taskbar is currently visually stashed.
*/
@@ -1070,19 +1209,11 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
return mControllers.taskbarStashController.isStashed();
}
/**
* Called when we detect a long press in the nav region before passing the gesture slop.
*
* @return Whether taskbar handled the long press, and thus should cancel the gesture.
*/
public boolean onLongPressToUnstashTaskbar() {
return mControllers.taskbarStashController.onLongPressToUnstashTaskbar();
}
/**
* Called when we want to unstash taskbar when user performs swipes up gesture.
*/
public void onSwipeToUnstashTaskbar() {
VibratorWrapper.INSTANCE.get(this).vibrateForTaskbarUnstash();
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(/* stash= */ false);
mControllers.taskbarEduTooltipController.hide();
}
@@ -1133,28 +1264,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
* @param animateForward Whether to animate towards the unstashed hint state or back to stashed.
*/
public void startTaskbarUnstashHint(boolean animateForward) {
// TODO(b/270395798): Clean up forceUnstash after removing long-press unstashing code.
startTaskbarUnstashHint(animateForward, /* forceUnstash = */ false);
}
/**
* 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.
*/
public void startTaskbarUnstashHint(boolean animateForward, boolean forceUnstash) {
// TODO(b/270395798): Clean up forceUnstash after removing long-press unstashing code.
mControllers.taskbarStashController.startUnstashHint(animateForward, forceUnstash);
}
/**
* Enables manual taskbar stashing. This method should only be used for tests that need to
* stash/unstash the taskbar.
*/
@VisibleForTesting
public void enableManualStashingDuringTests(boolean enableManualStashing) {
mControllers.taskbarStashController.enableManualStashingDuringTests(enableManualStashing);
mControllers.taskbarStashController.startUnstashHint(animateForward);
}
/**
@@ -1167,15 +1277,12 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
}
/**
* Unstashes the Taskbar if it is stashed. This method should only be used to unstash the
* taskbar at the end of a test.
* Unstashes the Taskbar if it is stashed.
*/
@VisibleForTesting
public void unstashTaskbarIfStashed() {
if (DisplayController.isTransientTaskbar(this)) {
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(false);
} else {
mControllers.taskbarStashController.onLongPressToUnstashTaskbar();
}
}
@@ -1212,7 +1319,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
TaskbarUIController uiController = mControllers.uiController;
if (uiController instanceof LauncherTaskbarUIController) {
((LauncherTaskbarUIController) uiController).addLauncherResumeAnimation(
((LauncherTaskbarUIController) uiController).addLauncherVisibilityChangedAnimation(
fullAnimation, duration);
}
mControllers.taskbarStashController.addUnstashToHotseatAnimation(fullAnimation, duration);
@@ -1249,12 +1356,16 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
mWindowLayoutParams.privateFlags &=
~WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
}
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
notifyUpdateLayoutParams();
}
void notifyUpdateLayoutParams() {
if (mDragLayer.isAttachedToWindow()) {
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
if (enableTaskbarNoRecreate()) {
mWindowManager.updateViewLayout(mDragLayer.getRootView(), mWindowLayoutParams);
} else {
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
}
}
}
@@ -29,26 +29,39 @@ import com.android.launcher3.Utilities
import com.android.launcher3.Utilities.mapRange
import com.android.launcher3.Utilities.mapToRange
import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
import com.android.launcher3.taskbar.TaskbarPinningController.Companion.PINNING_PERSISTENT
import com.android.launcher3.taskbar.TaskbarPinningController.Companion.PINNING_TRANSIENT
import com.android.launcher3.util.DisplayController
import kotlin.math.min
/** Helps draw the taskbar background, made up of a rectangle plus two inverted rounded corners. */
class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
private val isInSetup: Boolean = !context.isUserSetupComplete
private val DARK_THEME_SHADOW_ALPHA = 51f
private val LIGHT_THEME_SHADOW_ALPHA = 25f
private val maxTransientTaskbarHeight =
context.transientTaskbarDeviceProfile.taskbarHeight.toFloat()
private val maxPersistentTaskbarHeight =
context.persistentTaskbarDeviceProfile.taskbarHeight.toFloat()
var backgroundProgress =
if (DisplayController.isTransientTaskbar(context)) {
PINNING_TRANSIENT
} else {
PINNING_PERSISTENT
}
var isAnimatingPinning = false
val paint = Paint()
val lastDrawnTransientRect = RectF()
var backgroundHeight = context.deviceProfile.taskbarHeight.toFloat()
var translationYForSwipe = 0f
var translationYForStash = 0f
private var maxBackgroundHeight = context.deviceProfile.taskbarHeight.toFloat()
private val transientBackgroundBounds = context.transientTaskbarBounds
private val isTransientTaskbar = DisplayController.isTransientTaskbar(context)
private val shadowAlpha: Float
private var shadowBlur = 0f
private var keyShadowDistance = 0f
@@ -75,13 +88,6 @@ class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
paint.flags = Paint.ANTI_ALIAS_FLAG
paint.style = Paint.Style.FILL
if (isTransientTaskbar) {
val res = context.resources
bottomMargin = res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin)
shadowBlur = res.getDimension(R.dimen.transient_taskbar_shadow_blur)
keyShadowDistance = res.getDimension(R.dimen.transient_taskbar_key_shadow_distance)
}
shadowAlpha =
if (Utilities.isDarkTheme(context)) DARK_THEME_SHADOW_ALPHA
else LIGHT_THEME_SHADOW_ALPHA
@@ -90,10 +96,11 @@ class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
}
fun updateStashedHandleWidth(dp: DeviceProfile, res: Resources) {
stashedHandleWidth = res.getDimensionPixelSize(
stashedHandleWidth =
res.getDimensionPixelSize(
if (TaskbarManager.isPhoneMode(dp)) R.dimen.taskbar_stashed_small_screen
else R.dimen.taskbar_stashed_handle_width
)
)
}
/**
@@ -102,7 +109,7 @@ class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
* @param cornerRoundness 0 has no round corner, 1 has complete round corner.
*/
fun setCornerRoundness(cornerRoundness: Float) {
if (isTransientTaskbar && !transientBackgroundBounds.isEmpty) {
if (DisplayController.isTransientTaskbar(context) && !transientBackgroundBounds.isEmpty) {
return
}
@@ -126,63 +133,123 @@ class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
/** Draws the background with the given paint and height, on the provided canvas. */
fun draw(canvas: Canvas) {
if (isInSetup) return
val isTransientTaskbar = backgroundProgress == 0f
canvas.save()
if (!isTransientTaskbar || transientBackgroundBounds.isEmpty) {
canvas.translate(0f, canvas.height - backgroundHeight - bottomMargin)
// Draw the background behind taskbar content.
canvas.drawRect(0f, 0f, canvas.width.toFloat(), backgroundHeight, paint)
// Draw the inverted rounded corners above the taskbar.
canvas.translate(0f, -leftCornerRadius)
canvas.drawPath(invertedLeftCornerPath, paint)
canvas.translate(0f, leftCornerRadius)
canvas.translate(canvas.width - rightCornerRadius, -rightCornerRadius)
canvas.drawPath(invertedRightCornerPath, paint)
} else if (!isInSetup) {
// backgroundHeight is a value from [0...maxBackgroundHeight], so we can use it as a
// proxy to figure out the animation progress of the stash/unstash animation.
val progress = backgroundHeight / maxBackgroundHeight
// At progress 0, we draw the background as the stashed handle.
// At progress 1, we draw the background as the full taskbar.
val newBackgroundHeight =
mapRange(progress, stashedHandleHeight.toFloat(), maxBackgroundHeight)
val fullWidth = transientBackgroundBounds.width()
val newWidth = mapRange(progress, stashedHandleWidth.toFloat(), fullWidth.toFloat())
val halfWidthDelta = (fullWidth - newWidth) / 2f
val radius = newBackgroundHeight / 2f
val bottomMarginProgress = bottomMargin * ((1f - progress) / 2f)
// Aligns the bottom with the bottom of the stashed handle.
val bottom =
canvas.height - bottomMargin +
bottomMarginProgress +
translationYForSwipe +
translationYForStash +
-mapRange(1f - progress, 0f, stashedHandleHeight / 2f)
// Draw shadow.
val newShadowAlpha =
mapToRange(paint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
paint.setShadowLayer(
shadowBlur,
0f,
keyShadowDistance,
setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
)
lastDrawnTransientRect.set(
transientBackgroundBounds.left + halfWidthDelta,
bottom - newBackgroundHeight,
transientBackgroundBounds.right - halfWidthDelta,
bottom
)
val horizontalInset = fullWidth * widthInsetPercentage
lastDrawnTransientRect.inset(horizontalInset, 0f)
canvas.drawRoundRect(lastDrawnTransientRect, radius, radius, paint)
if (!isTransientTaskbar || transientBackgroundBounds.isEmpty || isAnimatingPinning) {
drawPersistentBackground(canvas)
}
canvas.restore()
canvas.save()
if (isAnimatingPinning || isTransientTaskbar) {
drawTransientBackground(canvas)
}
canvas.restore()
}
private fun drawPersistentBackground(canvas: Canvas) {
if (isAnimatingPinning) {
val persistentTaskbarHeight = maxPersistentTaskbarHeight * backgroundProgress
canvas.translate(0f, canvas.height - persistentTaskbarHeight)
// Draw the background behind taskbar content.
canvas.drawRect(0f, 0f, canvas.width.toFloat(), persistentTaskbarHeight, paint)
} else {
val persistentTaskbarHeight = min(maxPersistentTaskbarHeight, backgroundHeight)
canvas.translate(0f, canvas.height - persistentTaskbarHeight)
// Draw the background behind taskbar content.
canvas.drawRect(0f, 0f, canvas.width.toFloat(), persistentTaskbarHeight, paint)
}
// Draw the inverted rounded corners above the taskbar.
canvas.translate(0f, -leftCornerRadius)
canvas.drawPath(invertedLeftCornerPath, paint)
canvas.translate(0f, leftCornerRadius)
canvas.translate(canvas.width - rightCornerRadius, -rightCornerRadius)
canvas.drawPath(invertedRightCornerPath, paint)
}
private fun drawTransientBackground(canvas: Canvas) {
val res = context.resources
val transientTaskbarHeight = maxTransientTaskbarHeight * (1f - backgroundProgress)
val heightProgressWhileAnimating =
if (isAnimatingPinning) transientTaskbarHeight else backgroundHeight
var progress = heightProgressWhileAnimating / maxTransientTaskbarHeight
progress = Math.round(progress * 100f) / 100f
if (isAnimatingPinning) {
var scale = transientTaskbarHeight / maxTransientTaskbarHeight
scale = Math.round(scale * 100f) / 100f
bottomMargin =
mapRange(
scale,
0f,
res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin).toFloat()
)
.toInt()
shadowBlur =
mapRange(scale, 0f, res.getDimension(R.dimen.transient_taskbar_shadow_blur))
keyShadowDistance =
mapRange(scale, 0f, res.getDimension(R.dimen.transient_taskbar_key_shadow_distance))
} else {
bottomMargin = res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin)
shadowBlur = res.getDimension(R.dimen.transient_taskbar_shadow_blur)
keyShadowDistance = res.getDimension(R.dimen.transient_taskbar_key_shadow_distance)
}
// At progress 0, we draw the background as the stashed handle.
// At progress 1, we draw the background as the full taskbar.
// Min height capped to max persistent taskbar height for animation
val backgroundHeightWhileAnimating =
if (isAnimatingPinning) maxPersistentTaskbarHeight else stashedHandleHeight.toFloat()
val newBackgroundHeight =
mapRange(progress, backgroundHeightWhileAnimating, maxTransientTaskbarHeight)
val fullWidth = transientBackgroundBounds.width()
// .9f is here to restrict min width of the background while animating, so transient
// background keeps it pill shape until animation end.
val animationWidth =
if (DisplayController.isTransientTaskbar(context)) fullWidth.toFloat() * .9f
else fullWidth.toFloat()
val backgroundWidthWhileAnimating =
if (isAnimatingPinning) animationWidth else stashedHandleWidth.toFloat()
val newWidth = mapRange(progress, backgroundWidthWhileAnimating, fullWidth.toFloat())
val halfWidthDelta = (fullWidth - newWidth) / 2f
val radius = newBackgroundHeight / 2f
val bottomMarginProgress = bottomMargin * ((1f - progress) / 2f)
// Aligns the bottom with the bottom of the stashed handle.
val bottom =
canvas.height - bottomMargin +
bottomMarginProgress +
translationYForSwipe +
translationYForStash +
-mapRange(
1f - progress,
0f,
if (isAnimatingPinning) 0f else stashedHandleHeight / 2f
)
// Draw shadow.
val newShadowAlpha =
mapToRange(paint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
paint.setShadowLayer(
shadowBlur,
0f,
keyShadowDistance,
setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
)
lastDrawnTransientRect.set(
transientBackgroundBounds.left + halfWidthDelta,
bottom - newBackgroundHeight,
transientBackgroundBounds.right - halfWidthDelta,
bottom
)
val horizontalInset = fullWidth * widthInsetPercentage
lastDrawnTransientRect.inset(horizontalInset, 0f)
canvas.drawRoundRect(lastDrawnTransientRect, radius, radius, paint)
}
/**
@@ -62,7 +62,7 @@ public class TaskbarControllers {
public final TaskbarOverlayController taskbarOverlayController;
public final TaskbarEduTooltipController taskbarEduTooltipController;
public final KeyboardQuickSwitchController keyboardQuickSwitchController;
public final TaskbarDividerPopupController taskbarPinningController;
public final TaskbarPinningController taskbarPinningController;
public final Optional<BubbleControllers> bubbleControllers;
@Nullable private LoggableTaskbarController[] mControllersToLog = null;
@@ -110,7 +110,7 @@ public class TaskbarControllers {
TaskbarRecentAppsController taskbarRecentAppsController,
TaskbarEduTooltipController taskbarEduTooltipController,
KeyboardQuickSwitchController keyboardQuickSwitchController,
TaskbarDividerPopupController taskbarPinningController,
TaskbarPinningController taskbarPinningController,
Optional<BubbleControllers> bubbleControllers) {
this.taskbarActivityContext = taskbarActivityContext;
this.taskbarDragController = taskbarDragController;
@@ -171,7 +171,7 @@ public class TaskbarControllers {
taskbarTranslationController.init(this);
taskbarEduTooltipController.init(this);
keyboardQuickSwitchController.init(this);
taskbarPinningController.init(this);
taskbarPinningController.init(this, mSharedState);
bubbleControllers.ifPresent(controllers -> controllers.init(this));
mControllersToLog = new LoggableTaskbarController[] {
@@ -1,73 +0,0 @@
/*
* 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.view.View
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
import com.android.launcher3.taskbar.TaskbarDividerPopupView.Companion.createAndPopulate
import java.io.PrintWriter
/** Controls taskbar pinning through a popup view. */
class TaskbarDividerPopupController(private val context: TaskbarActivityContext) :
TaskbarControllers.LoggableTaskbarController {
private lateinit var controllers: TaskbarControllers
private val launcherPrefs = LauncherPrefs.get(context)
fun init(taskbarControllers: TaskbarControllers) {
controllers = taskbarControllers
}
fun showPinningView(view: View) {
context.isTaskbarWindowFullscreen = true
view.post {
val popupView = createAndPopulate(view, context)
popupView.requestFocus()
popupView.onCloseCallback =
callback@{ didPreferenceChange ->
context.dragLayer.post { context.onPopupVisibilityChanged(false) }
if (!didPreferenceChange) {
return@callback
}
if (launcherPrefs.get(TASKBAR_PINNING)) {
animateTransientToPersistentTaskbar()
} else {
animatePersistentToTransientTaskbar()
}
}
popupView.changePreference = {
launcherPrefs.put(TASKBAR_PINNING, !launcherPrefs.get(TASKBAR_PINNING))
}
context.onPopupVisibilityChanged(true)
popupView.show()
}
}
// TODO(b/265436799): provide animation/transition from transient taskbar to persistent one
private fun animateTransientToPersistentTaskbar() {}
// TODO(b/265436799): provide animation/transition from persistent taskbar to transient one
private fun animatePersistentToTransientTaskbar() {}
override fun dumpLogs(prefix: String, pw: PrintWriter) {
pw.println(prefix + "TaskbarPinningController:")
}
}
@@ -15,22 +15,29 @@
*/
package com.android.launcher3.taskbar
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Rect
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
import android.util.Property
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.widget.LinearLayout
import android.widget.Switch
import androidx.core.view.postDelayed
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.launcher3.R
import com.android.launcher3.popup.ArrowPopup
import com.android.launcher3.popup.RoundedArrowDrawable
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.Themes
import com.android.launcher3.views.ActivityContext
/** Popup view with arrow for taskbar pinning */
class TaskbarDividerPopupView<T : TaskbarActivityContext>
@@ -42,7 +49,8 @@ constructor(
) : ArrowPopup<T>(context, attrs, defStyleAttr) {
companion object {
private const val TAG = "TaskbarDividerPopupView"
private const val DIVIDER_POPUP_CLOSING_DELAY = 500L
private const val DIVIDER_POPUP_CLOSING_DELAY = 333L
private const val DIVIDER_POPUP_CLOSING_ANIMATION_DURATION = 83L
@JvmStatic
fun createAndPopulate(
@@ -59,10 +67,11 @@ constructor(
return taskMenuViewWithArrow.populateForView(view)
}
}
private lateinit var dividerView: View
private val menuWidth =
context.resources.getDimensionPixelSize(R.dimen.taskbar_pinning_popup_menu_width)
resources.getDimensionPixelSize(R.dimen.taskbar_pinning_popup_menu_width)
private val popupCornerRadius = Themes.getDialogCornerRadius(context)
private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width)
private val arrowHeight = resources.getDimension(R.dimen.popup_arrow_height)
@@ -70,16 +79,12 @@ constructor(
private var alwaysShowTaskbarOn = !DisplayController.isTransientTaskbar(context)
private var didPreferenceChange = false
private var verticalOffsetForPopupView =
resources.getDimensionPixelSize(R.dimen.taskbar_pinning_popup_menu_vertical_margin)
/** Callback invoked when the pinning popup view is closing. */
var onCloseCallback: (preferenceChanged: Boolean) -> Unit = {}
/**
* Callback invoked when the user preference changes in popup view. Preference change will be
* based upon current value stored in [LauncherPrefs] for `TASKBAR_PINNING`
*/
var changePreference: () -> Unit = {}
init {
// This synchronizes the arrow and menu to open at the same time
mOpenChildFadeStartDelay = mOpenFadeStartDelay
@@ -99,11 +104,22 @@ constructor(
super.onFinishInflate()
val taskbarSwitchOption = requireViewById<LinearLayout>(R.id.taskbar_switch_option)
val alwaysShowTaskbarSwitch = requireViewById<Switch>(R.id.taskbar_pinning_switch)
val taskbarVisibilityIcon = requireViewById<View>(R.id.taskbar_pinning_visibility_icon)
alwaysShowTaskbarSwitch.isChecked = alwaysShowTaskbarOn
taskbarSwitchOption.setOnClickListener {
alwaysShowTaskbarSwitch.isClickable = true
alwaysShowTaskbarSwitch.isChecked = !alwaysShowTaskbarOn
onClickAlwaysShowTaskbarSwitchOption()
if (ActivityContext.lookupContext<TaskbarActivityContext>(context).isGestureNav) {
taskbarSwitchOption.setOnClickListener {
alwaysShowTaskbarSwitch.isClickable = true
alwaysShowTaskbarSwitch.isChecked = !alwaysShowTaskbarOn
onClickAlwaysShowTaskbarSwitchOption()
}
} else {
alwaysShowTaskbarSwitch.isEnabled = false
}
if (!alwaysShowTaskbarSwitch.isEnabled) {
taskbarVisibilityIcon.background.setTint(
resources.getColor(android.R.color.system_neutral2_500, context.theme)
)
}
}
@@ -176,15 +192,80 @@ constructor(
}
}
override fun closeComplete() {
override fun getExtraVerticalOffset(): Int {
return (mActivityContext.deviceProfile.taskbarHeight -
mActivityContext.deviceProfile.taskbarIconSize) / 2 + verticalOffsetForPopupView
}
override fun animateClose() {
if (!mIsOpen) {
return
}
if (mOpenCloseAnimator != null) {
mOpenCloseAnimator.cancel()
}
mIsOpen = false
mOpenCloseAnimator = getCloseAnimator()
mOpenCloseAnimator.addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
mOpenCloseAnimator = null
if (mDeferContainerRemoval) {
setVisibility(INVISIBLE)
} else {
closeComplete()
}
}
}
)
onCloseCallback(didPreferenceChange)
super.closeComplete()
onCloseCallback = {}
mOpenCloseAnimator.start()
}
private fun getCloseAnimator(): AnimatorSet {
val alphaValues = floatArrayOf(1f, 0f)
val translateYValue =
if (!alwaysShowTaskbarOn) verticalOffsetForPopupView else -verticalOffsetForPopupView
val alpha = getAnimatorOfFloat(this, ALPHA, *alphaValues)
val arrowAlpha = getAnimatorOfFloat(mArrow, ALPHA, *alphaValues)
val translateY =
ObjectAnimator.ofFloat(
this,
TRANSLATION_Y,
*floatArrayOf(this.translationY, this.translationY + translateYValue)
)
val arrowTranslateY =
ObjectAnimator.ofFloat(
mArrow,
TRANSLATION_Y,
*floatArrayOf(mArrow.translationY, mArrow.translationY + translateYValue)
)
val animatorSet = AnimatorSet()
animatorSet.playTogether(alpha, arrowAlpha, translateY, arrowTranslateY)
return animatorSet
}
private fun getAnimatorOfFloat(
view: View,
property: Property<View, Float>,
vararg values: Float
): Animator {
val animator: Animator = ObjectAnimator.ofFloat(view, property, *values)
animator.setDuration(DIVIDER_POPUP_CLOSING_ANIMATION_DURATION)
animator.interpolator = EMPHASIZED_ACCELERATE
return animator
}
private fun onClickAlwaysShowTaskbarSwitchOption() {
didPreferenceChange = true
changePreference()
// Allow switch animation to finish and then close the popup.
postDelayed(DIVIDER_POPUP_CLOSING_DELAY) { close(true) }
postDelayed(DIVIDER_POPUP_CLOSING_DELAY) {
if (isOpen) {
close(true)
}
}
}
}
@@ -21,6 +21,8 @@ import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APP
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.LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS;
import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -64,6 +66,7 @@ import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -259,6 +262,8 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source,
ItemInfo dragInfo, Rect dragRegion, float initialDragViewScale,
float dragViewScaleOnDrop, DragOptions options) {
mActivity.hideKeyboard();
mOptions = options;
mRegistrationX = mMotionDown.x - dragLayerX;
@@ -624,7 +629,9 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
if (tag instanceof ItemInfo) {
ItemInfo item = (ItemInfo) tag;
if (item.container == CONTAINER_ALL_APPS || item.container == CONTAINER_PREDICTION) {
if (item.container == CONTAINER_ALL_APPS
|| item.container == CONTAINER_PREDICTION
|| isInSearchResultContainer(item)) {
if (mDisallowGlobalDrag) {
// We're dragging in taskbarAllApps, we don't have folders or shortcuts
return iconView;
@@ -646,6 +653,13 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
return iconView;
}
private static boolean isInSearchResultContainer(ItemInfo item) {
ContainerInfo containerInfo = item.getContainerInfo();
return containerInfo.getContainerCase() == EXTENDED_CONTAINERS
&& containerInfo.getExtendedContainers().getContainerCase()
== DEVICE_SEARCH_RESULT_CONTAINER;
}
private void setupReturnDragAnimator(float fromX, float fromY, View originalView,
TaskbarReturnPropertiesListener animListener) {
// Finish any pending return animation before starting a new return
@@ -18,6 +18,8 @@ package com.android.launcher3.taskbar;
import static android.view.KeyEvent.ACTION_UP;
import static android.view.KeyEvent.KEYCODE_BACK;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.RectF;
@@ -73,6 +75,8 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
private SafeCloseable mViewCaptureCloseable;
private float mTaskbarBackgroundOffset;
private float mTaskbarBackgroundProgress;
private boolean mIsAnimatingTaskbarPinning = false;
private final MultiPropertyFactory<TaskbarDragLayer> mTaskbarBackgroundAlpha;
@@ -124,7 +128,7 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
}
protected void onDestroy() {
onDestroy(!TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW);
onDestroy(!ENABLE_TASKBAR_NAVBAR_UNIFICATION);
}
@Override
@@ -162,10 +166,19 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
* (1f - mTaskbarBackgroundOffset);
mBackgroundRenderer.setBackgroundHeight(backgroundHeight);
mBackgroundRenderer.setBackgroundProgress(mTaskbarBackgroundProgress);
mBackgroundRenderer.draw(canvas);
super.dispatchDraw(canvas);
}
/**
* Sets animation boolean when taskbar pinning animation starts or stops.
*/
public void setAnimatingTaskbarPinning(boolean animatingTaskbarPinning) {
mIsAnimatingTaskbarPinning = animatingTaskbarPinning;
mBackgroundRenderer.setAnimatingPinning(mIsAnimatingTaskbarPinning);
}
protected MultiProperty getBackgroundRendererAlpha() {
return mTaskbarBackgroundAlpha.get(INDEX_ALL_OTHER_STATES);
}
@@ -174,6 +187,15 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
return mTaskbarBackgroundAlpha.get(INDEX_STASH_ANIM);
}
/**
* Sets the value for taskbar background switching between persistent and transient backgrounds.
* @param progress 0 is transient background, 1 is persistent background.
*/
protected void setTaskbarBackgroundProgress(float progress) {
mTaskbarBackgroundProgress = progress;
invalidate();
}
/**
* Sets the translation of the background color behind all the Taskbar contents.
* @param offset 0 is fully onscreen, 1 is fully offscreen.
@@ -15,6 +15,9 @@
*/
package com.android.launcher3.taskbar;
import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT;
import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
@@ -24,6 +27,7 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.util.DimensionUtils;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.TouchController;
@@ -58,6 +62,9 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
// changes the inset visibility.
private final AnimatedFloat mTaskbarAlpha = new AnimatedFloat(this::updateTaskbarAlpha);
private final AnimatedFloat mTaskbarBackgroundProgress = new AnimatedFloat(
this::updateTaskbarBackgroundProgress);
// Initialized in init.
private TaskbarControllers mControllers;
private TaskbarStashViaTouchController mTaskbarStashViaTouchController;
@@ -83,6 +90,10 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
mOnBackgroundNavButtonColorIntensity = mControllers.navbarButtonsViewController
.getOnTaskbarBackgroundNavButtonColorOverride();
mTaskbarBackgroundProgress.updateValue(DisplayController.isTransientTaskbar(mActivity)
? PINNING_TRANSIENT
: PINNING_PERSISTENT);
mBgTaskbar.value = 1;
mKeyguardBgTaskbar.value = 1;
mNotificationShadeBgTaskbar.value = 1;
@@ -138,6 +149,11 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
return mBgOffset;
}
// AnimatedFloat is for animating between pinned and transient taskbar
public AnimatedFloat getTaskbarBackgroundProgress() {
return mTaskbarBackgroundProgress;
}
public AnimatedFloat getTaskbarAlpha() {
return mTaskbarAlpha;
}
@@ -180,10 +196,13 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
private void updateBackgroundOffset() {
mTaskbarDragLayer.setTaskbarBackgroundOffset(mBgOffset.value);
updateOnBackgroundNavButtonColorIntensity();
}
private void updateTaskbarBackgroundProgress() {
mTaskbarDragLayer.setTaskbarBackgroundProgress(mTaskbarBackgroundProgress.value);
}
private void updateTaskbarAlpha() {
mTaskbarDragLayer.setAlpha(mTaskbarAlpha.value);
}
@@ -29,8 +29,10 @@ import androidx.core.view.updateLayoutParams
import com.airbnb.lottie.LottieAnimationView
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.config.FeatureFlags.enableTaskbarPinningEdu
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
import com.android.launcher3.taskbar.TaskbarManager.isPhoneMode
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP
import com.android.quickstep.util.LottieAnimationColorUtils
@@ -40,16 +42,19 @@ import java.io.PrintWriter
const val TOOLTIP_STEP_SWIPE = 0
/** Second EDU step for explaining Taskbar functionality when unstashed. */
const val TOOLTIP_STEP_FEATURES = 1
/** Third EDU step for explaining Taskbar pinning. */
const val TOOLTIP_STEP_PINNING = 2
/**
* EDU is completed.
*
* This value should match the maximum count for [TASKBAR_EDU_TOOLTIP_STEP].
*/
const val TOOLTIP_STEP_NONE = 2
const val TOOLTIP_STEP_NONE = 3
/** Current step in the tooltip EDU flow. */
@Retention(AnnotationRetention.SOURCE)
@IntDef(TOOLTIP_STEP_SWIPE, TOOLTIP_STEP_FEATURES, TOOLTIP_STEP_NONE)
@IntDef(TOOLTIP_STEP_SWIPE, TOOLTIP_STEP_FEATURES, TOOLTIP_STEP_PINNING, TOOLTIP_STEP_NONE)
annotation class TaskbarEduTooltipStep
/** Controls stepping through the Taskbar tooltip EDU. */
@@ -57,7 +62,7 @@ class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
LoggableTaskbarController {
private val isTooltipEnabled: Boolean
get() = !Utilities.isRunningInTestHarness()
get() = !Utilities.isRunningInTestHarness() && !isPhoneMode(activityContext.deviceProfile)
private val isOpen: Boolean
get() = tooltip?.isOpen ?: false
val isBeforeTooltipFeaturesStep: Boolean
@@ -67,11 +72,10 @@ class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
@TaskbarEduTooltipStep
var tooltipStep: Int
get() {
return activityContext.onboardingPrefs?.getCount(TASKBAR_EDU_TOOLTIP_STEP)
?: TOOLTIP_STEP_NONE
return TASKBAR_EDU_TOOLTIP_STEP.get(activityContext)
}
private set(step) {
activityContext.onboardingPrefs?.setEventCount(step, TASKBAR_EDU_TOOLTIP_STEP)
TASKBAR_EDU_TOOLTIP_STEP.set(step, activityContext)
}
private var tooltip: TaskbarEduTooltip? = null
@@ -114,19 +118,19 @@ class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
tooltip?.run {
val splitscreenAnim = requireViewById<LottieAnimationView>(R.id.splitscreen_animation)
val suggestionsAnim = requireViewById<LottieAnimationView>(R.id.suggestions_animation)
val settingsAnim = requireViewById<LottieAnimationView>(R.id.settings_animation)
val settingsEdu = requireViewById<View>(R.id.settings_edu)
val pinningAnim = requireViewById<LottieAnimationView>(R.id.pinning_animation)
val pinningEdu = requireViewById<View>(R.id.pinning_edu)
splitscreenAnim.supportLightTheme()
suggestionsAnim.supportLightTheme()
settingsAnim.supportLightTheme()
pinningAnim.supportLightTheme()
if (DisplayController.isTransientTaskbar(activityContext)) {
splitscreenAnim.setAnimation(R.raw.taskbar_edu_splitscreen_transient)
suggestionsAnim.setAnimation(R.raw.taskbar_edu_suggestions_transient)
settingsEdu.visibility = GONE
pinningEdu.visibility = if (enableTaskbarPinningEdu()) VISIBLE else GONE
} else {
splitscreenAnim.setAnimation(R.raw.taskbar_edu_splitscreen_persistent)
suggestionsAnim.setAnimation(R.raw.taskbar_edu_suggestions_persistent)
settingsEdu.visibility = VISIBLE
pinningEdu.visibility = GONE
}
// Set up layout parameters.
@@ -135,13 +139,16 @@ class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
if (DisplayController.isTransientTaskbar(activityContext)) {
width =
resources.getDimensionPixelSize(
R.dimen.taskbar_edu_features_tooltip_width_transient
if (enableTaskbarPinningEdu())
R.dimen.taskbar_edu_features_tooltip_width_with_three_features
else R.dimen.taskbar_edu_features_tooltip_width_with_two_features
)
bottomMargin += activityContext.deviceProfile.taskbarHeight
} else {
width =
resources.getDimensionPixelSize(
R.dimen.taskbar_edu_features_tooltip_width_persistent
R.dimen.taskbar_edu_features_tooltip_width_with_two_features
)
}
}
@@ -251,5 +258,5 @@ private fun LottieAnimationView.supportLightTheme() {
return
}
LottieAnimationColorUtils.updateColors(this, DARK_TO_LIGHT_COLORS, context.theme)
LottieAnimationColorUtils.updateToColorResources(this, DARK_TO_LIGHT_COLORS, context.theme)
}
@@ -22,7 +22,7 @@ import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBIL
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static com.android.launcher3.taskbar.NavbarButtonsViewController.ALPHA_INDEX_IMMERSIVE_MODE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IMMERSIVE_MODE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
import android.os.Bundle;
import android.os.Handler;
@@ -84,7 +84,7 @@ public class TaskbarForceVisibleImmersiveController implements TouchController {
/** Update values tracked via sysui flags. */
public void updateSysuiFlags(int sysuiFlags) {
mIsImmersiveMode = (sysuiFlags & SYSUI_STATE_IMMERSIVE_MODE) != 0;
mIsImmersiveMode = (sysuiFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) == 0;
if (mContext.isNavBarForceVisible()) {
if (mIsImmersiveMode) {
startIconDimming();
@@ -158,8 +158,7 @@ public class TaskbarForceVisibleImmersiveController implements TouchController {
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (!isNavbarShownInImmersiveMode()
|| mControllers.taskbarStashController.supportsManualStashing()) {
if (!isNavbarShownInImmersiveMode()) {
return false;
}
return onControllerTouchEvent(ev);
@@ -35,8 +35,6 @@ 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;
@@ -51,8 +49,7 @@ import com.android.launcher3.views.ArrowTipView;
*/
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_REVEAL_DURATION = 250;
private static final int HOVER_TOOL_TIP_EXIT_DURATION = 150;
private final Handler mHoverToolTipHandler = new Handler(Looper.getMainLooper());
@@ -84,6 +81,12 @@ public class TaskbarHoverToolTipController implements View.OnHoverListener {
R.style.ArrowTipTaskbarStyle);
mHoverToolTipView = new ArrowTipView(arrowContextWrapper, /* isPointingUp = */ false,
R.layout.arrow_toast);
int verticalPadding = arrowContextWrapper.getResources().getDimensionPixelSize(
R.dimen.taskbar_tooltip_vertical_padding);
int horizontalPadding = arrowContextWrapper.getResources().getDimensionPixelSize(
R.dimen.taskbar_tooltip_horizontal_padding);
mHoverToolTipView.findViewById(R.id.text).setPadding(horizontalPadding, verticalPadding,
horizontalPadding, verticalPadding);
AnimatorSet hoverCloseAnimator = new AnimatorSet();
ObjectAnimator textCloseAnimator = ObjectAnimator.ofInt(mHoverToolTipView, TEXT_ALPHA, 0);
@@ -101,17 +104,18 @@ public class TaskbarHoverToolTipController implements View.OnHoverListener {
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);
ObjectAnimator textOpenAnimator =
ObjectAnimator.ofInt(mHoverToolTipView, TEXT_ALPHA, 0, 255);
textOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.15f, 0.75f));
ObjectAnimator scaleOpenAnimator =
ObjectAnimator.ofFloat(mHoverToolTipView, SCALE_Y, 0f, 1f);
scaleOpenAnimator.setInterpolator(Interpolators.EMPHASIZED);
ObjectAnimator alphaOpenAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, ALPHA, 1f);
alphaOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.1f, 0.33f));
ObjectAnimator alphaOpenAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, ALPHA, 0f, 1f);
alphaOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0f, 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);
@@ -120,8 +124,6 @@ public class TaskbarHoverToolTipController implements View.OnHoverListener {
mHoverToolTipView.setPivotY(bottom);
mHoverToolTipView.setY(mTaskbarView.getTop() - (bottom - top));
});
mHoverToolTipView.setScaleY(0f);
mHoverToolTipView.setAlpha(0f);
}
@Override
@@ -147,8 +149,7 @@ public class TaskbarHoverToolTipController implements View.OnHoverListener {
}
private void startRevealHoverToolTip() {
mHoverToolTipHandler.postDelayed(mRevealHoverToolTipRunnable,
HOVER_TOOL_TIP_REVEAL_START_DELAY);
mHoverToolTipHandler.post(mRevealHoverToolTipRunnable);
}
private void revealHoverToolTip() {
@@ -158,14 +159,12 @@ public class TaskbarHoverToolTipController implements View.OnHoverListener {
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);
@@ -15,9 +15,9 @@
*/
package com.android.launcher3.taskbar
import android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR
import android.graphics.Insets
import android.graphics.Region
import android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR
import android.os.Binder
import android.os.IBinder
import android.view.Gravity
@@ -41,6 +41,8 @@ import com.android.internal.policy.GestureNavigationSettingsObserver
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.launcher3.anim.AlphaUpdateListener
import com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION
import com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
import com.android.launcher3.util.DisplayController
import java.io.PrintWriter
@@ -97,7 +99,14 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
0
}
windowLayoutParams.providedInsets = getProvidedInsets(insetsRoundedCornerFlag)
windowLayoutParams.providedInsets =
if (enableTaskbarNoRecreate()) {
getProvidedInsets(controllers.sharedState!!.insetsFrameProviders!!,
insetsRoundedCornerFlag)
} else {
getProvidedInsets(insetsRoundedCornerFlag)
}
if (!context.isGestureNav) {
if (windowLayoutParams.paramsForRotation != null) {
for (layoutParams in windowLayoutParams.paramsForRotation) {
@@ -153,6 +162,26 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
context.notifyUpdateLayoutParams()
}
/**
* This is for when ENABLE_TASKBAR_NO_RECREATION is enabled. We generate one instance of
* providedInsets and use it across the entire lifecycle of TaskbarManager. The only thing
* we need to reset is nav bar flags based on insetsRoundedCornerFlag.
*/
private fun getProvidedInsets(providedInsets: Array<InsetsFrameProvider>,
insetsRoundedCornerFlag: Int): Array<InsetsFrameProvider> {
val navBarsFlag =
(if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
for (provider in providedInsets) {
if (provider.type == navigationBars()) {
provider.setFlags(
navBarsFlag,
FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER
)
}
}
return providedInsets
}
/**
* The inset types and number of insets provided have to match for both gesture nav and button
* nav. The values and the order of the elements in array are allowed to differ.
@@ -197,7 +226,6 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
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) {
@@ -208,6 +236,12 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
val imeInsetsSizeOverride =
arrayOf(
InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
InsetsFrameProvider.InsetsSizeOverride(TYPE_VOICE_INTERACTION,
// No-op override to keep the size and types in sync with the
// override below (insetsSizeOverrides must have the same length and
// types after the window is added according to
// WindowManagerService#relayoutWindow)
provider.insetsSize)
)
// Use 0 tappableElement insets for the VoiceInteractionWindow when gesture nav is enabled.
val visInsetsSizeForTappableElement =
@@ -216,12 +250,11 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
val insetsSizeOverrideForTappableElement =
arrayOf(
InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
InsetsFrameProvider.InsetsSizeOverride(
TYPE_VOICE_INTERACTION,
InsetsFrameProvider.InsetsSizeOverride(TYPE_VOICE_INTERACTION,
visInsetsSizeForTappableElement
),
)
if ((context.isGestureNav || TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
if ((context.isGestureNav || ENABLE_TASKBAR_NAVBAR_UNIFICATION)
&& provider.type == tappableElement()) {
provider.insetsSizeOverrides = insetsSizeOverrideForTappableElement
} else if (provider.type != systemGestures()) {
@@ -17,7 +17,9 @@ package com.android.launcher3.taskbar;
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED;
import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_OVERVIEW;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
@@ -44,6 +46,7 @@ import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
@@ -66,13 +69,13 @@ public class TaskbarLauncherStateController {
private static final String TAG = TaskbarLauncherStateController.class.getSimpleName();
private static final boolean DEBUG = false;
/** Launcher activity is resumed and focused. */
public static final int FLAG_RESUMED = 1 << 0;
/** Launcher activity is visible and focused. */
public static final int FLAG_VISIBLE = 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_VISIBLE being set.
**/
public static final int FLAG_TRANSITION_TO_RESUMED = 1 << 1;
public static final int FLAG_TRANSITION_TO_VISIBLE = 1 << 1;
/**
* Set while the launcher state machine is performing a state transition, see {@link
@@ -116,7 +119,7 @@ public class TaskbarLauncherStateController {
*/
private static final int FLAG_TASKBAR_HIDDEN = 1 << 6;
private static final int FLAGS_LAUNCHER_ACTIVE = FLAG_RESUMED | FLAG_TRANSITION_TO_RESUMED;
private static final int FLAGS_LAUNCHER_ACTIVE = FLAG_VISIBLE | FLAG_TRANSITION_TO_VISIBLE;
/** Equivalent to an int with all 1s for binary operation purposes */
private static final int FLAGS_ALL = ~0;
@@ -202,19 +205,32 @@ public class TaskbarLauncherStateController {
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;
boolean disallowLongClick =
FeatureFlags.enableSplitContextually()
? mLauncher.isSplitSelectionEnabled()
: finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
com.android.launcher3.taskbar.Utilities.setOverviewDragState(
mControllers, finalState.disallowTaskbarGlobalDrag(),
disallowLongClick, finalState.allowTaskbarInitialSplitSelection());
}
};
/**
* Callback for when launcher state transition completes after user swipes to home.
* @param finalState The final state of the transition.
*/
public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) {
// TODO(b/279514548) Cleans up bad state that can occur when user interacts with
// taskbar on top of transparent activity.
if (!FeatureFlags.enableHomeTransitionListener()
&& (finalState == LauncherState.NORMAL)
&& mLauncher.hasBeenResumed()) {
updateStateForFlag(FLAG_VISIBLE, true);
applyState();
}
}
/** Initializes the controller instance, and applies the initial state immediately. */
public void init(TaskbarControllers controllers, QuickstepLauncher launcher,
int sysuiStateFlags) {
@@ -277,7 +293,7 @@ public class TaskbarLauncherStateController {
}
stashController.updateStateForFlag(FLAG_IN_APP, false);
updateStateForFlag(FLAG_TRANSITION_TO_RESUMED, true);
updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, true);
animatorSet.play(stashController.createApplyStateAnimator(duration));
animatorSet.play(applyState(duration, false));
@@ -416,6 +432,9 @@ public class TaskbarLauncherStateController {
controllers.bubbleStashController.setBubblesShowingOnOverview(onOverview);
});
mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_OVERVIEW,
mLauncherState == LauncherState.OVERVIEW);
AnimatorSet animatorSet = new AnimatorSet();
if (hasAnyFlag(changedFlags, FLAG_LAUNCHER_IN_STATE_TRANSITION)) {
@@ -425,7 +444,7 @@ public class TaskbarLauncherStateController {
if (launcherTransitionCompleted
&& mLauncherState == LauncherState.QUICK_SWITCH_FROM_HOME) {
// We're about to be paused, set immediately to ensure seamless handoff.
updateStateForFlag(FLAG_RESUMED, false);
updateStateForFlag(FLAG_VISIBLE, false);
applyState(0 /* duration */);
}
if (mLauncherState == LauncherState.NORMAL) {
@@ -715,6 +734,7 @@ public class TaskbarLauncherStateController {
}
mIconAlphaForHome.setValue(alpha);
boolean hotseatVisible = alpha == 0
|| isPhoneMode(mLauncher.getDeviceProfile())
|| (!mControllers.uiController.isHotseatIconOnTopWhenAligned()
&& mIconAlignment.value > 0);
/*
@@ -751,10 +771,10 @@ public class TaskbarLauncherStateController {
mTaskBarRecentsAnimationListener = null;
((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null);
// Update the resumed state immediately to ensure a seamless handoff
boolean launcherResumed = !finishedToApp;
updateStateForFlag(FLAG_TRANSITION_TO_RESUMED, false);
updateStateForFlag(FLAG_RESUMED, launcherResumed);
// Update the visible state immediately to ensure a seamless handoff
boolean launcherVisible = !finishedToApp;
updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, false);
updateStateForFlag(FLAG_VISIBLE, launcherVisible);
applyState();
TaskbarStashController controller = mControllers.taskbarStashController;
@@ -768,8 +788,8 @@ public class TaskbarLauncherStateController {
private static String getStateString(int flags) {
StringJoiner result = new StringJoiner("|");
appendFlag(result, flags, FLAG_RESUMED, "resumed");
appendFlag(result, flags, FLAG_TRANSITION_TO_RESUMED, "transition_to_resumed");
appendFlag(result, flags, FLAG_VISIBLE, "flag_visible");
appendFlag(result, flags, FLAG_TRANSITION_TO_VISIBLE, "transition_to_visible");
appendFlag(result, flags, FLAG_LAUNCHER_IN_STATE_TRANSITION,
"launcher_in_state_transition");
appendFlag(result, flags, FLAG_AWAKE, "awake");
@@ -18,33 +18,37 @@ package com.android.launcher3.taskbar;
import static android.content.Context.RECEIVER_NOT_EXPORTED;
import static android.content.pm.PackageManager.FEATURE_PC;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
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.BaseActivity.EVENT_DESTROYED;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
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 static com.android.quickstep.util.SystemActionConstants.ACTION_SHOW_TASKBAR;
import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
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;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -53,12 +57,11 @@ import androidx.annotation.VisibleForTesting;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
import com.android.launcher3.LauncherAppState;
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.ActivityLifecycleCallbacksAdapter;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.quickstep.RecentsActivity;
@@ -68,6 +71,7 @@ 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;
import com.android.wm.shell.Flags;
import java.io.PrintWriter;
import java.util.StringJoiner;
@@ -95,9 +99,6 @@ public class TaskbarManager {
| 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);
private static final Uri USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(
Settings.Secure.USER_SETUP_COMPLETE);
@@ -105,6 +106,10 @@ public class TaskbarManager {
Settings.Secure.NAV_BAR_KIDS_MODE);
private final Context mContext;
private final @Nullable Context mNavigationBarPanelContext;
private WindowManager mWindowManager;
private FrameLayout mTaskbarRootLayout;
private boolean mAddedWindow;
private final TaskbarNavButtonController mNavButtonController;
private final ComponentCallbacks mComponentCallbacks;
@@ -135,44 +140,28 @@ public class TaskbarManager {
private boolean mUserUnlocked = false;
public static final int SYSTEM_ACTION_ID_TASKBAR = 499;
/**
* For Taskbar broadcast intent filter.
*/
public static final String ACTION_SHOW_TASKBAR = "ACTION_SHOW_TASKBAR";
private final SimpleBroadcastReceiver mTaskbarBroadcastReceiver =
new SimpleBroadcastReceiver(this::showTaskbarFromBroadcast);
private final SharedPreferences.OnSharedPreferenceChangeListener
mTaskbarPinningPreferenceChangeListener = (sharedPreferences, key) -> {
if (TASKBAR_PINNING_KEY.equals(key)) {
recreateTaskbar();
}
};
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);
}
};
private final Runnable mActivityOnDestroyCallback = new Runnable() {
@Override
public void run() {
if (mActivity != null) {
mActivity.removeOnDeviceProfileChangeListener(
mDebugActivityDeviceProfileChanged);
Log.d(TASKBAR_NOT_DESTROYED_TAG,
"unregistering activity lifecycle callbacks from "
+ "onActivityDestroyed.");
mActivity.removeEventCallback(EVENT_DESTROYED, this);
}
mActivity = null;
debugWhyTaskbarNotDestroyed("clearActivity");
if (mTaskbarActivityContext != null) {
mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
}
mUnfoldProgressProvider.setSourceProvider(null);
}
};
UnfoldTransitionProgressProvider.TransitionProgressListener mUnfoldTransitionProgressListener =
new UnfoldTransitionProgressProvider.TransitionProgressListener() {
@@ -207,7 +196,27 @@ public class TaskbarManager {
public TaskbarManager(TouchInteractionService service) {
Display display =
service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
mContext = service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null);
mContext = service.createWindowContext(display,
ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL,
null);
mNavigationBarPanelContext = ENABLE_TASKBAR_NAVBAR_UNIFICATION
? service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
: null;
if (enableTaskbarNoRecreate()) {
mWindowManager = mContext.getSystemService(WindowManager.class);
mTaskbarRootLayout = new FrameLayout(mContext) {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// The motion events can be outside the view bounds of task bar, and hence
// manually dispatching them to the drag layer here.
if (mTaskbarActivityContext != null
&& mTaskbarActivityContext.getDragLayer().isAttachedToWindow()) {
return mTaskbarActivityContext.getDragLayer().dispatchTouchEvent(ev);
}
return super.dispatchTouchEvent(ev);
}
};
}
mNavButtonController = new TaskbarNavButtonController(service,
SystemUiProxy.INSTANCE.get(mContext), new Handler(),
AssistUtils.newInstance(mContext));
@@ -244,7 +253,7 @@ public class TaskbarManager {
destroyExistingTaskbar();
} else {
if (dp != null && isTaskbarPresent(dp)) {
if (FLAG_HIDE_NAVBAR_WINDOW) {
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
// Re-initialize for screen size change? Should this be done
// by looking at screen-size change flag in configDiff in the
// block above?
@@ -288,13 +297,16 @@ public class TaskbarManager {
private void destroyExistingTaskbar() {
debugWhyTaskbarNotDestroyed("destroyExistingTaskbar: " + mTaskbarActivityContext);
if (mTaskbarActivityContext != null) {
LauncherPrefs.get(mContext).removeListener(mTaskbarPinningPreferenceChangeListener,
TASKBAR_PINNING);
mTaskbarActivityContext.onDestroy();
if (!FLAG_HIDE_NAVBAR_WINDOW) {
if (!ENABLE_TASKBAR_NAVBAR_UNIFICATION || enableTaskbarNoRecreate()) {
mTaskbarActivityContext = null;
}
}
DeviceProfile dp = mUserUnlocked ?
LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
if (dp == null || !isTaskbarPresent(dp)) {
removeTaskbarRootViewFromWindow();
}
}
/**
@@ -322,7 +334,7 @@ public class TaskbarManager {
return;
}
mTaskbarActivityContext.toggleAllApps();
mTaskbarActivityContext.toggleAllAppsSearch();
}
/**
@@ -343,6 +355,7 @@ public class TaskbarManager {
mUserUnlocked = true;
LauncherAppState.getIDP(mContext).addOnChangeListener(mIdpChangeListener);
recreateTaskbar();
addTaskbarRootViewToWindow();
}
/**
@@ -358,7 +371,7 @@ public class TaskbarManager {
mActivity.addOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
Log.d(TASKBAR_NOT_DESTROYED_TAG,
"registering activity lifecycle callbacks from setActivity().");
mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
mActivity.addEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback);
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
getUnfoldTransitionProgressProviderForActivity(activity);
if (unfoldTransitionProgressProvider != null) {
@@ -417,7 +430,7 @@ public class TaskbarManager {
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
+ " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
+ " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent));
if (!isTaskbarEnabled) {
SystemUiProxy.INSTANCE.get(mContext)
@@ -425,13 +438,15 @@ public class TaskbarManager {
return;
}
if (mTaskbarActivityContext == null) {
mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp,
mNavButtonController,
mUnfoldProgressProvider);
if (enableTaskbarNoRecreate() || mTaskbarActivityContext == null) {
mTaskbarActivityContext = new TaskbarActivityContext(mContext,
mNavigationBarPanelContext, dp, mNavButtonController,
mUnfoldProgressProvider);
} else {
mTaskbarActivityContext.updateDeviceProfile(dp);
}
mSharedState.startTaskbarVariantIsTransient =
DisplayController.isTransientTaskbar(mTaskbarActivityContext);
mTaskbarActivityContext.init(mSharedState);
if (mActivity != null) {
@@ -439,9 +454,12 @@ public class TaskbarManager {
createTaskbarUIControllerForActivity(mActivity));
}
// We to wait until user unlocks the device to attach listener.
LauncherPrefs.get(mContext).addListener(mTaskbarPinningPreferenceChangeListener,
TASKBAR_PINNING);
if (enableTaskbarNoRecreate()) {
addTaskbarRootViewToWindow();
mTaskbarRootLayout.removeAllViews();
mTaskbarRootLayout.addView(mTaskbarActivityContext.getDragLayer());
mTaskbarActivityContext.notifyUpdateLayoutParams();
}
} finally {
Trace.endSection();
}
@@ -479,7 +497,7 @@ public class TaskbarManager {
* and we are using a single window for taskbar and navbar.
*/
public static boolean isPhoneMode(DeviceProfile deviceProfile) {
return TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW && deviceProfile.isPhone;
return ENABLE_TASKBAR_NAVBAR_UNIFICATION && deviceProfile.isPhone;
}
/**
@@ -491,7 +509,7 @@ public class TaskbarManager {
}
private boolean isTaskbarPresent(DeviceProfile deviceProfile) {
return FLAG_HIDE_NAVBAR_WINDOW || deviceProfile.isTaskbarPresent;
return ENABLE_TASKBAR_NAVBAR_UNIFICATION || deviceProfile.isTaskbarPresent;
}
public void onRotationProposal(int rotation, boolean isValid) {
@@ -530,7 +548,7 @@ public class TaskbarManager {
Log.d(TASKBAR_NOT_DESTROYED_TAG,
"unregistering activity lifecycle callbacks from "
+ "removeActivityCallbackAndListeners().");
mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
mActivity.removeEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback);
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
getUnfoldTransitionProgressProviderForActivity(mActivity);
if (unfoldTransitionProgressProvider != null) {
@@ -573,6 +591,21 @@ public class TaskbarManager {
}
}
private void addTaskbarRootViewToWindow() {
if (enableTaskbarNoRecreate() && !mAddedWindow && mTaskbarActivityContext != null) {
mWindowManager.addView(mTaskbarRootLayout,
mTaskbarActivityContext.getWindowLayoutParams());
mAddedWindow = true;
}
}
private void removeTaskbarRootViewFromWindow() {
if (enableTaskbarNoRecreate() && mAddedWindow) {
mWindowManager.removeViewImmediate(mTaskbarRootLayout);
mAddedWindow = false;
}
}
/** Temp logs for b/254119092. */
public void debugWhyTaskbarNotDestroyed(String debugReason) {
StringJoiner log = new StringJoiner("\n");
@@ -27,6 +27,7 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP;
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
@@ -52,7 +53,6 @@ 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;
@@ -274,7 +274,7 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
private void navigateHome() {
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
if (isDesktopModeSupported()) {
DesktopVisibilityController desktopVisibilityController =
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
if (desktopVisibilityController != null) {
@@ -0,0 +1,134 @@
/*
* 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.animation.AnimatorSet
import android.annotation.SuppressLint
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.core.animation.doOnEnd
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN
import com.android.launcher3.taskbar.TaskbarDividerPopupView.Companion.createAndPopulate
import java.io.PrintWriter
/** Controls taskbar pinning through a popup view. */
class TaskbarPinningController(private val context: TaskbarActivityContext) :
TaskbarControllers.LoggableTaskbarController {
private lateinit var controllers: TaskbarControllers
private lateinit var taskbarSharedState: TaskbarSharedState
private lateinit var launcherPrefs: LauncherPrefs
private val statsLogManager = context.statsLogManager
@VisibleForTesting var isAnimatingTaskbarPinning = false
@VisibleForTesting lateinit var onCloseCallback: (preferenceChanged: Boolean) -> Unit
@SuppressLint("VisibleForTests")
fun init(taskbarControllers: TaskbarControllers, sharedState: TaskbarSharedState) {
controllers = taskbarControllers
taskbarSharedState = sharedState
launcherPrefs = context.launcherPrefs
onCloseCallback =
fun(didPreferenceChange: Boolean) {
statsLogManager.logger().log(LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE)
context.dragLayer.post { context.onPopupVisibilityChanged(false) }
if (!didPreferenceChange) {
return
}
val animateToValue =
if (!launcherPrefs.get(TASKBAR_PINNING)) {
PINNING_PERSISTENT
} else {
PINNING_TRANSIENT
}
taskbarSharedState.taskbarWasPinned = animateToValue == PINNING_TRANSIENT
animateTaskbarPinning(animateToValue)
}
}
fun showPinningView(view: View) {
context.isTaskbarWindowFullscreen = true
view.post {
val popupView = getPopupView(view)
popupView.requestFocus()
popupView.onCloseCallback = onCloseCallback
context.onPopupVisibilityChanged(true)
popupView.show()
statsLogManager.logger().log(LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN)
}
}
@VisibleForTesting
fun getPopupView(view: View): TaskbarDividerPopupView<*> {
return createAndPopulate(view, context)
}
@VisibleForTesting
fun animateTaskbarPinning(animateToValue: Float) {
val taskbarViewController = controllers.taskbarViewController
val animatorSet =
getAnimatorSetForTaskbarPinningAnimation(animateToValue).apply {
doOnEnd { recreateTaskbarAndUpdatePinningValue() }
duration = PINNING_ANIMATION_DURATION
}
controllers.taskbarOverlayController.hideWindow()
updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(true)
taskbarViewController.animateAwayNotificationDotsDuringTaskbarPinningAnimation()
animatorSet.start()
}
@VisibleForTesting
fun getAnimatorSetForTaskbarPinningAnimation(animateToValue: Float): AnimatorSet {
val animatorSet = AnimatorSet()
val taskbarViewController = controllers.taskbarViewController
val dragLayerController = controllers.taskbarDragLayerController
animatorSet.playTogether(
dragLayerController.taskbarBackgroundProgress.animateToValue(animateToValue),
taskbarViewController.taskbarIconTranslationYForPinning.animateToValue(animateToValue),
taskbarViewController.taskbarIconScaleForPinning.animateToValue(animateToValue),
taskbarViewController.taskbarIconTranslationXForPinning.animateToValue(animateToValue)
)
return animatorSet
}
private fun updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(isAnimating: Boolean) {
isAnimatingTaskbarPinning = isAnimating
context.dragLayer.setAnimatingTaskbarPinning(isAnimating)
}
@VisibleForTesting
fun recreateTaskbarAndUpdatePinningValue() {
updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(false)
launcherPrefs.put(TASKBAR_PINNING, !launcherPrefs.get(TASKBAR_PINNING))
}
override fun dumpLogs(prefix: String, pw: PrintWriter) {
pw.println(prefix + "TaskbarPinningController:")
pw.println("$prefix\tisAnimatingTaskbarPinning=$isAnimatingTaskbarPinning")
pw.println("$prefix\tTASKBAR_PINNING shared pref =" + launcherPrefs.get(TASKBAR_PINNING))
}
companion object {
const val PINNING_PERSISTENT = 1f
const val PINNING_TRANSIENT = 0f
const val PINNING_ANIMATION_DURATION = 500L
}
}
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.taskbar;
import static com.android.launcher3.config.FeatureFlags.ENABLE_MATERIAL_U_POPUP;
import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
import android.content.Intent;
@@ -163,19 +162,9 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (ENABLE_MATERIAL_U_POPUP.get()) {
container = (PopupContainerWithArrow) context.getLayoutInflater().inflate(
R.layout.popup_container_material_u, context.getDragLayer(), false);
container.populateAndShowRowsMaterialU(icon, deepShortcutCount, systemShortcuts);
} else {
container = (PopupContainerWithArrow) context.getLayoutInflater().inflate(
container = (PopupContainerWithArrow) context.getLayoutInflater().inflate(
R.layout.popup_container, context.getDragLayer(), false);
container.populateAndShow(
icon,
deepShortcutCount,
mPopupDataProvider.getNotificationKeysForItem(item),
systemShortcuts);
}
container.populateAndShowRows(icon, deepShortcutCount, systemShortcuts);
container.addOnAttachStateChangeListener(
new PopupLiveUpdateHandler<BaseTaskbarContext>(context, container) {
@@ -70,6 +70,10 @@ public class TaskbarScrimView extends View {
invalidate();
}
protected float getScrimAlpha() {
return mRenderer.getPaint().getAlpha() / 255f;
}
/**
* Sets the roundness of the round corner above Taskbar.
* @param cornerRoundness 0 has no round corner, 1 has complete round corner.
@@ -15,9 +15,13 @@
*/
package com.android.launcher3.taskbar;
import static com.android.launcher3.taskbar.bubbles.BubbleBarController.BUBBLE_BAR_ENABLED;
import static android.view.View.VISIBLE;
import static com.android.launcher3.taskbar.bubbles.BubbleBarController.isBubbleBarEnabled;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
import static com.android.wm.shell.common.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
import android.animation.ObjectAnimator;
import android.view.animation.Interpolator;
@@ -35,13 +39,13 @@ import java.io.PrintWriter;
public class TaskbarScrimViewController implements TaskbarControllers.LoggableTaskbarController,
TaskbarControllers.BackgroundRendererController {
private static final float SCRIM_ALPHA = 0.6f;
private static final Interpolator SCRIM_ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
private static final Interpolator SCRIM_ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
private final TaskbarActivityContext mActivity;
private final TaskbarScrimView mScrimView;
private boolean mTaskbarVisible;
private int mSysUiStateFlags;
// Alpha property for the scrim.
private final AnimatedFloat mScrimAlpha = new AnimatedFloat(this::updateScrimAlpha);
@@ -61,36 +65,61 @@ public class TaskbarScrimViewController implements TaskbarControllers.LoggableTa
mControllers = controllers;
}
/**
* Called when the taskbar visibility changes.
*
* @param visibility the current visibility of {@link TaskbarView}.
*/
public void onTaskbarVisibilityChanged(int visibility) {
mTaskbarVisible = visibility == VISIBLE;
if (shouldShowScrim()) {
showScrim(true, getScrimAlpha(), false /* skipAnim */);
} else if (mScrimView.getScrimAlpha() > 0f) {
showScrim(false, 0, false /* skipAnim */);
}
}
/**
* Updates the scrim state based on the flags.
*/
public void updateStateForSysuiFlags(int stateFlags, boolean skipAnim) {
if (BUBBLE_BAR_ENABLED && DisplayController.isTransientTaskbar(mActivity)) {
if (isBubbleBarEnabled() && 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;
mSysUiStateFlags = stateFlags;
showScrim(shouldShowScrim(), getScrimAlpha(), skipAnim);
}
private boolean shouldShowScrim() {
final boolean bubblesExpanded = (mSysUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) != 0;
boolean isShadeVisible = (mSysUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE) != 0;
return bubblesExpanded && !mControllers.navbarButtonsViewController.isImeVisible()
&& !isShadeVisible
&& !mControllers.taskbarStashController.isStashed()
&& mTaskbarVisible;
}
private float getScrimAlpha() {
final boolean manageMenuExpanded =
(stateFlags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
final boolean showScrim = !mControllers.navbarButtonsViewController.isImeVisible()
&& bubblesExpanded
&& mControllers.taskbarStashController.isTaskbarVisibleAndNotStashing();
final float scrimAlpha = manageMenuExpanded
(mSysUiStateFlags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
return manageMenuExpanded
// When manage menu shows there's the first scrim and second scrim so figure out
// what the total transparency would be.
? (SCRIM_ALPHA + (SCRIM_ALPHA * (1 - SCRIM_ALPHA)))
: showScrim ? SCRIM_ALPHA : 0;
showScrim(showScrim, scrimAlpha, skipAnim);
? (BUBBLE_EXPANDED_SCRIM_ALPHA + (BUBBLE_EXPANDED_SCRIM_ALPHA
* (1 - BUBBLE_EXPANDED_SCRIM_ALPHA)))
: shouldShowScrim() ? BUBBLE_EXPANDED_SCRIM_ALPHA : 0;
}
private void showScrim(boolean showScrim, float alpha, boolean skipAnim) {
mScrimView.setOnClickListener(showScrim ? (view) -> onClick() : null);
mScrimView.setClickable(showScrim);
ObjectAnimator anim = mScrimAlpha.animateToValue(showScrim ? alpha : 0);
anim.setInterpolator(showScrim ? SCRIM_ALPHA_IN : SCRIM_ALPHA_OUT);
anim.start();
if (skipAnim) {
anim.end();
mScrimView.setScrimAlpha(alpha);
} else {
ObjectAnimator anim = mScrimAlpha.animateToValue(showScrim ? alpha : 0);
anim.setInterpolator(showScrim ? SCRIM_ALPHA_IN : SCRIM_ALPHA_OUT);
anim.start();
}
}
@@ -15,15 +15,28 @@
*/
package com.android.launcher3.taskbar;
import static android.view.InsetsFrameProvider.SOURCE_DISPLAY;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.systemGestures;
import static android.view.WindowInsets.Type.tappableElement;
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.DISPLAY_PROGRESS_COUNT;
import android.app.PendingIntent;
import android.os.Binder;
import android.os.IBinder;
import android.view.InsetsFrameProvider;
/**
* State shared across different taskbar instance
*/
public class TaskbarSharedState {
private final IBinder mInsetsOwner = new Binder();
private static int INDEX_LEFT = 0;
private static int INDEX_RIGHT = 1;
// TaskbarManager#onSystemUiFlagsChanged
public int sysuiStateFlags;
@@ -48,4 +61,33 @@ public class TaskbarSharedState {
// Taskbar System Action
public PendingIntent taskbarSystemActionPendingIntent;
public final InsetsFrameProvider[] insetsFrameProviders = new InsetsFrameProvider[] {
new InsetsFrameProvider(mInsetsOwner, 0, navigationBars()),
new InsetsFrameProvider(mInsetsOwner, 0, tappableElement()),
new InsetsFrameProvider(mInsetsOwner, 0, mandatorySystemGestures()),
new InsetsFrameProvider(mInsetsOwner, INDEX_LEFT, systemGestures())
.setSource(SOURCE_DISPLAY),
new InsetsFrameProvider(mInsetsOwner, INDEX_RIGHT, systemGestures())
.setSource(SOURCE_DISPLAY)
};
// Allows us to shift translation logic when doing taskbar pinning animation.
public boolean startTaskbarVariantIsTransient = true;
// To track if taskbar was pinned using taskbar pinning feature at the time of recreate,
// so we can unstash transient taskbar when we un-pinning taskbar.
private boolean mTaskbarWasPinned = false;
public boolean getTaskbarWasPinned() {
return mTaskbarWasPinned;
}
public void setTaskbarWasPinned(boolean taskbarWasPinned) {
mTaskbarWasPinned = taskbarWasPinned;
}
// To track if taskbar was stashed / unstashed between configuration changes (which recreates
// the task bar).
public Boolean taskbarWasStashedAuto = true;
}
@@ -34,6 +34,7 @@ import com.android.launcher3.R;
import com.android.launcher3.accessibility.BaseAccessibilityDelegate;
import com.android.launcher3.logging.StatsLogManager;
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.notification.NotificationListener;
import com.android.launcher3.util.ShortcutUtil;
@@ -84,9 +85,9 @@ public class TaskbarShortcutMenuAccessibilityDelegate
@Override
protected boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) {
if (item instanceof WorkspaceItemInfo
if (item instanceof ItemInfoWithIcon
&& (action == MOVE_TO_TOP_OR_LEFT || action == MOVE_TO_BOTTOM_OR_RIGHT)) {
WorkspaceItemInfo info = (WorkspaceItemInfo) item;
ItemInfoWithIcon info = (ItemInfoWithIcon) item;
int side = action == MOVE_TO_TOP_OR_LEFT
? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -97,10 +98,11 @@ public class TaskbarShortcutMenuAccessibilityDelegate
.withInstanceId(instanceIds.second)
.log(getLogEventForPosition(side));
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
&& item instanceof WorkspaceItemInfo) {
SystemUiProxy.INSTANCE.get(mContext).startShortcut(
info.getIntent().getPackage(),
info.getDeepShortcutId(),
((WorkspaceItemInfo) info).getDeepShortcutId(),
side,
/* bundleOpts= */ null,
info.user,
@@ -15,24 +15,21 @@
*/
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.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;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_SHOW;
import static com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED;
import static com.android.launcher3.taskbar.TaskbarManager.SYSTEM_ACTION_ID_TASKBAR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
@@ -43,7 +40,6 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.app.RemoteAction;
import android.content.SharedPreferences;
import android.graphics.drawable.Icon;
import android.os.SystemClock;
import android.util.Log;
@@ -61,9 +57,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.launcher3.Alarm;
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.AnimatedFloat;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.util.DisplayController;
@@ -85,26 +79,26 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
private static final boolean DEBUG = false;
public static final int FLAG_IN_APP = 1 << 0;
public static final int FLAG_STASHED_IN_APP_MANUAL = 1 << 1; // long press, persisted
public static final int FLAG_STASHED_IN_APP_SYSUI = 1 << 2; // shade open, ...
public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 3; // setup wizard and AllSetActivity
public static final int FLAG_STASHED_IN_APP_IME = 1 << 4; // IME is visible
public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 5;
public static final int FLAG_STASHED_IN_TASKBAR_ALL_APPS = 1 << 6; // All apps is visible.
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_DEVICE_LOCKED = 1 << 11; // device is locked: keyguard, ...
public static final int FLAG_STASHED_IN_APP_SYSUI = 1 << 1; // shade open, ...
public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 2; // setup wizard and AllSetActivity
public static final int FLAG_STASHED_IN_APP_IME = 1 << 3; // IME is visible
public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 4;
public static final int FLAG_STASHED_IN_TASKBAR_ALL_APPS = 1 << 5; // All apps is visible.
public static final int FLAG_IN_SETUP = 1 << 6; // In the Setup Wizard
public static final int FLAG_STASHED_SMALL_SCREEN = 1 << 7; // phone screen gesture nav, stashed
public static final int FLAG_STASHED_IN_APP_AUTO = 1 << 8; // Autohide (transient taskbar).
public static final int FLAG_STASHED_SYSUI = 1 << 9; // app pinning,...
public static final int FLAG_STASHED_DEVICE_LOCKED = 1 << 10; // device is locked: keyguard, ...
public static final int FLAG_IN_OVERVIEW = 1 << 11; // launcher is in overview
// 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.
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;
private static final int FLAGS_STASHED_IN_APP = 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.
@@ -164,21 +158,11 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
*/
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.
*/
private static final float STASHED_TASKBAR_HINT_SCALE = 0.9f;
/**
* The scale that the stashed handle animates to when hinting towards the unstashed state.
*/
private static final float UNSTASHED_TASKBAR_HANDLE_HINT_SCALE = 1.1f;
/**
* The SharedPreferences key for whether user has manually stashed the taskbar.
*/
private static final String SHARED_PREFS_STASHED_KEY = "taskbar_is_stashed";
/**
* Whether taskbar should be stashed out of the box.
*/
@@ -222,7 +206,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
private @interface StashAnimation {}
private final TaskbarActivityContext mActivity;
private final SharedPreferences mPrefs;
private final int mStashedHeight;
private final int mUnstashedHeight;
private final SystemUiProxy mSystemUiProxy;
@@ -251,8 +234,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
private boolean mIsImeShowing;
private boolean mIsImeSwitcherShowing;
private boolean mEnableManualStashingDuringTests = false;
private final Alarm mTimeoutAlarm = new Alarm();
private boolean mEnableBlockingTimeoutDuringTests = false;
@@ -272,12 +253,18 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
public TaskbarStashController(TaskbarActivityContext activity) {
mActivity = activity;
mPrefs = LauncherPrefs.getPrefs(mActivity);
mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class);
mUnstashedHeight = mActivity.getDeviceProfile().taskbarHeight;
mStashedHeight = mActivity.getDeviceProfile().stashedTaskbarHeight;
if (isPhoneMode()) {
mUnstashedHeight = mActivity.getResources().getDimensionPixelSize(
R.dimen.taskbar_phone_size);
mStashedHeight = mActivity.getResources().getDimensionPixelSize(
R.dimen.taskbar_stashed_size);
} else {
mUnstashedHeight = mActivity.getDeviceProfile().taskbarHeight;
mStashedHeight = mActivity.getDeviceProfile().stashedTaskbarHeight;
}
}
/**
@@ -320,16 +307,13 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
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).
boolean isManuallyStashedInApp = supportsVisualStashing()
&& !isTransientTaskbar
&& !ENABLE_TASKBAR_PINNING.get()
&& mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
boolean isInSetup = !mActivity.isUserSetupComplete() || setupUIVisible;
updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, isTransientTaskbar);
boolean isStashedInAppAuto =
isTransientTaskbar && !mTaskbarSharedState.getTaskbarWasPinned();
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
isStashedInAppAuto = isStashedInAppAuto && mTaskbarSharedState.taskbarWasStashedAuto;
}
updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, isStashedInAppAuto);
updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup);
updateStateForFlag(FLAG_IN_SETUP, isInSetup);
updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, isPhoneMode()
@@ -338,6 +322,10 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
// us that we're paused until a bit later. This avoids flickering upon recreating taskbar.
updateStateForFlag(FLAG_IN_APP, true);
applyState(/* duration = */ 0);
if (mTaskbarSharedState.getTaskbarWasPinned()
|| !mTaskbarSharedState.taskbarWasStashedAuto) {
tryStartTaskbarTimeout();
}
notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp());
}
@@ -349,28 +337,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
return !mActivity.isThreeButtonNav() && mControllers.uiController.supportsVisualStashing();
}
/**
* 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)) {
return false;
}
return supportsVisualStashing()
&& isInApp()
&& (!Utilities.isRunningInTestHarness() || mEnableManualStashingDuringTests)
&& !DisplayController.isTransientTaskbar(mActivity);
}
/**
* Enables support for manual stashing. This should only be used to add this functionality
* to Launcher specific tests.
*/
@VisibleForTesting
public void enableManualStashingDuringTests(boolean enableManualStashing) {
mEnableManualStashingDuringTests = enableManualStashing;
}
/**
* Enables the auto timeout for taskbar stashing. This method should only be used for taskbar
* testing.
@@ -532,6 +498,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
if (hasAnyFlag(FLAG_STASHED_IN_APP_AUTO) != stash) {
mTaskbarSharedState.taskbarWasStashedAuto = stash;
updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, stash);
applyState();
}
@@ -565,53 +532,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
/* 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
// taskbar, we use an OnLongClickListener on TaskbarView instead.
return false;
}
if (!canCurrentlyManuallyUnstash()) {
return false;
}
if (updateAndAnimateIsManuallyStashedInApp(false)) {
mControllers.taskbarActivityContext.getDragLayer().performHapticFeedback(LONG_PRESS);
return true;
}
return false;
}
/**
* 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);
}
/**
* Updates whether we should stash the taskbar when in apps, and animates to the changed state.
* @return Whether we started an animation to either be newly stashed or unstashed.
*/
public boolean updateAndAnimateIsManuallyStashedInApp(boolean isManuallyStashedInApp) {
if (!supportsManualStashing()) {
return false;
}
if (hasAnyFlag(FLAG_STASHED_IN_APP_MANUAL) != isManuallyStashedInApp) {
mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_KEY, isManuallyStashedInApp).apply();
updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
applyState();
return true;
}
return false;
}
/** Toggles the Taskbar's stash state. */
public void toggleTaskbarStash() {
if (!DisplayController.isTransientTaskbar(mActivity) || !hasAnyFlag(FLAGS_IN_APP)) return;
@@ -898,21 +818,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
}
});
}
/**
* Creates and starts a partial stash animation, hinting at the new state that will trigger when
* long press is detected.
* @param animateForward Whether we are going towards the new stashed state or returning to the
* unstashed state.
*/
public void startStashHint(boolean animateForward) {
if (isStashed() || !supportsManualStashing()) {
// Already stashed, no need to hint in that direction.
return;
}
mIconScaleForStash.animateToValue(
animateForward ? STASHED_TASKBAR_HINT_SCALE : 1)
.setDuration(TASKBAR_HINT_STASH_DURATION).start();
}
/**
* Creates and starts a partial unstash animation, hinting at the new state that will trigger
@@ -920,19 +825,12 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
*
* @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.
*/
protected void startUnstashHint(boolean animateForward, boolean forceUnstash) {
protected void startUnstashHint(boolean animateForward) {
if (!isStashed()) {
// Already unstashed, no need to hint in that direction.
return;
}
// TODO(b/270395798): Clean up after removing long-press unstashing code path.
if (!canCurrentlyManuallyUnstash() && !forceUnstash) {
// If any other flags are causing us to be stashed, long press won't cause us to
// unstash, so don't hint that it will.
return;
}
mTaskbarStashedHandleHintScale.animateToValue(
animateForward ? UNSTASHED_TASKBAR_HANDLE_HINT_SCALE : 1)
.setDuration(TASKBAR_HINT_STASH_DURATION).start();
@@ -1015,9 +913,12 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
updateStateForFlag(FLAG_STASHED_IN_APP_SYSUI, hasAnyFlag(systemUiStateFlags,
SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE));
updateStateForFlag(FLAG_STASHED_SYSUI,
hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING));
boolean stashForBubbles = hasAnyFlag(FLAG_IN_OVERVIEW)
&& hasAnyFlag(systemUiStateFlags, SYSUI_STATE_BUBBLES_EXPANDED)
&& DisplayController.isTransientTaskbar(mActivity);
updateStateForFlag(FLAG_STASHED_SYSUI,
hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING) || stashForBubbles);
boolean isLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED)
&& !hasAnyFlag(systemUiStateFlags, SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY);
updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, isLocked);
@@ -1040,10 +941,10 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
* * in small screen AND
* * 3 button nav AND
* * landscape (or seascape)
* We do not stash if taskbar is transient
* We do not stash if taskbar is transient or hardware keyboard is active.
*/
private boolean shouldStashForIme() {
if (DisplayController.isTransientTaskbar(mActivity)) {
if (DisplayController.isTransientTaskbar(mActivity) || mActivity.isHardwareKeyboard()) {
return false;
}
return (mIsImeShowing || mIsImeSwitcherShowing) &&
@@ -1082,13 +983,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
mControllers.taskbarAutohideSuspendController.updateFlag(
TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER, !isInApp());
}
if (hasAnyFlag(changedFlags, FLAG_STASHED_IN_APP_MANUAL)) {
if (hasAnyFlag(FLAG_STASHED_IN_APP_MANUAL)) {
mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_LONGPRESS_HIDE);
} else {
mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_LONGPRESS_SHOW);
}
}
if (hasAnyFlag(changedFlags, FLAG_STASHED_IN_APP_AUTO)) {
mActivity.getStatsLogManager().logger().log(hasAnyFlag(FLAG_STASHED_IN_APP_AUTO)
? LAUNCHER_TRANSIENT_TASKBAR_HIDE
@@ -1212,7 +1106,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
private static String getStateString(int flags) {
StringJoiner sj = new StringJoiner("|");
appendFlag(sj, flags, FLAGS_IN_APP, "FLAG_IN_APP");
appendFlag(sj, flags, FLAG_STASHED_IN_APP_MANUAL, "FLAG_STASHED_IN_APP_MANUAL");
appendFlag(sj, flags, FLAG_STASHED_IN_APP_SYSUI, "FLAG_STASHED_IN_APP_SYSUI");
appendFlag(sj, flags, FLAG_STASHED_IN_APP_SETUP, "FLAG_STASHED_IN_APP_SETUP");
appendFlag(sj, flags, FLAG_STASHED_IN_APP_IME, "FLAG_STASHED_IN_APP_IME");
@@ -30,6 +30,7 @@ import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -43,7 +44,6 @@ 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;
@@ -156,7 +156,7 @@ public class TaskbarUIController {
mControllers.taskbarActivityContext.startTranslationSpring();
}
/*
/**
* @param ev MotionEvent in screen coordinates.
* @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
*/
@@ -165,6 +165,14 @@ public class TaskbarUIController {
|| mControllers.navbarButtonsViewController.isEventOverAnyItem(ev);
}
/** Checks if the given {@link MotionEvent} is over the bubble bar stash handle. */
public boolean isEventOverBubbleBarStashHandle(MotionEvent ev) {
return mControllers.bubbleControllers.map(
bubbleControllers ->
bubbleControllers.bubbleStashController.isEventOverStashHandle(ev))
.orElse(false);
}
/**
* Returns true if icons should be aligned to hotseat in the current transition.
*/
@@ -209,6 +217,7 @@ public class TaskbarUIController {
recentsView.getSplitSelectController().findLastActiveTasksAndRunCallback(
Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()),
false /* findExactPairMatch */,
foundTasks -> {
@Nullable Task foundTask = foundTasks.get(0);
splitSelectSource.alreadyRunningTaskId = foundTask == null
@@ -227,6 +236,7 @@ public class TaskbarUIController {
RecentsView recents = getRecentsView();
recents.getSplitSelectController().findLastActiveTasksAndRunCallback(
Collections.singletonList(info.getComponentKey()),
false /* findExactPairMatch */,
foundTasks -> {
@Nullable Task foundTask = foundTasks.get(0);
if (foundTask != null) {
@@ -290,11 +300,9 @@ public class TaskbarUIController {
}
/**
* Launches the focused task in splitscreen.
*
* No-op if the view is not yet open.
* Launches the given task in split-screen.
*/
public void launchSplitTasks(@NonNull View taskview, @NonNull GroupTask groupTask) { }
public void launchSplitTasks(@NonNull GroupTask groupTask) { }
/**
* Returns the matching view (if any) in the taskbar.
@@ -322,6 +330,14 @@ public class TaskbarUIController {
return null;
}
/**
* Callback for when launcher state transition completes after user swipes to home.
* @param finalState The final state of the transition.
*/
public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) {
// Overridden
}
/**
* Refreshes the resumed state of this ui controller.
*/
@@ -336,4 +352,7 @@ public class TaskbarUIController {
.stream()
.map(mControllers.taskbarPopupController::createSplitShortcutFactory);
}
/** Adjusts the hotseat for the bubble bar. */
public void adjustHotseatForBubbleBar(boolean isBubbleBarVisible) {}
}
@@ -18,7 +18,11 @@ 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.Flags.enableCursorHoverStates;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import android.content.Context;
@@ -27,12 +31,15 @@ import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.InputDevice;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import androidx.annotation.DimenRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -42,7 +49,7 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.icons.ThemedIconDrawable;
import com.android.launcher3.model.data.FolderInfo;
@@ -90,11 +97,9 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
// Only non-null when device supports having an All Apps button.
private @Nullable IconButtonView mTaskbarDivider;
private View mQsb;
private final View mQsb;
private float mTransientTaskbarMinWidth;
private float mTransientTaskbarAllAppsButtonTranslationXOffset;
private final float mTransientTaskbarMinWidth;
private boolean mShouldTryStartAlign;
@@ -120,17 +125,17 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivityContext)
&& !TaskbarManager.isPhoneMode(mActivityContext.getDeviceProfile());
mIsRtl = Utilities.isRtl(resources);
mTransientTaskbarMinWidth = mContext.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);
mTransientTaskbarMinWidth = resources.getDimension(R.dimen.transient_taskbar_min_width);
onDeviceProfileChanged(mActivityContext.getDeviceProfile());
int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
int actualIconSize = mActivityContext.getDeviceProfile().taskbarIconSize;
if (enableTaskbarPinning()) {
DeviceProfile deviceProfile = mActivityContext.getTransientTaskbarDeviceProfile();
actualIconSize = deviceProfile.taskbarIconSize;
}
int visualIconSize = (int) (actualIconSize * ICON_VISIBLE_AREA_FACTOR);
mIconTouchSize = Math.max(actualIconSize,
@@ -138,8 +143,11 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
// We layout the icons to be of mIconTouchSize in width and height
mItemMarginLeftRight = actualMargin - (mIconTouchSize - visualIconSize) / 2;
mItemPadding = (mIconTouchSize - actualIconSize) / 2;
// We always layout taskbar as a transient taskbar when we have taskbar pinning feature on,
// then we scale and translate the icons to match persistent taskbar designs, so we use
// taskbar icon size from current device profile to calculate correct item padding.
mItemPadding = (mIconTouchSize - mActivityContext.getDeviceProfile().taskbarIconSize) / 2;
mFolderLeaveBehindColor = Themes.getAttrColor(mActivityContext,
android.R.attr.textColorTertiary);
@@ -149,15 +157,13 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
if (!mActivityContext.getPackageManager().hasSystemFeature(FEATURE_PC)) {
mAllAppsButton = (IconButtonView) LayoutInflater.from(context)
.inflate(R.layout.taskbar_all_apps_button, this, false);
mAllAppsButton.setIconDrawable(resources.getDrawable(isTransientTaskbar
? R.drawable.ic_transient_taskbar_all_apps_button
: R.drawable.ic_taskbar_all_apps_button));
mAllAppsButton.setScaleX(mIsRtl ? -1 : 1);
mAllAppsButton.setIconDrawable(resources.getDrawable(
getAllAppsButton(isTransientTaskbar)));
mAllAppsButton.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
mAllAppsButton.setForegroundTint(
mActivityContext.getColor(R.color.all_apps_button_color));
if (FeatureFlags.ENABLE_TASKBAR_PINNING.get()) {
if (enableTaskbarPinning()) {
mTaskbarDivider = (IconButtonView) LayoutInflater.from(context).inflate(
R.layout.taskbar_divider,
this, false);
@@ -171,6 +177,42 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
}
@DrawableRes
private int getAllAppsButton(boolean isTransientTaskbar) {
boolean shouldSelectTransientIcon =
(isTransientTaskbar || enableTaskbarPinning())
&& !mActivityContext.isThreeButtonNav();
if (ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
return shouldSelectTransientIcon
? R.drawable.ic_transient_taskbar_all_apps_search_button
: R.drawable.ic_taskbar_all_apps_search_button;
} else {
return shouldSelectTransientIcon
? R.drawable.ic_transient_taskbar_all_apps_button
: R.drawable.ic_taskbar_all_apps_button;
}
}
@DimenRes
public int getAllAppsButtonTranslationXOffset(boolean isTransientTaskbar) {
if (isTransientTaskbar) {
return R.dimen.transient_taskbar_all_apps_button_translation_x_offset;
} else {
return ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()
? R.dimen.taskbar_all_apps_search_button_translation_x_offset
: R.dimen.taskbar_all_apps_button_translation_x_offset;
}
}
@Override
public void setVisibility(int visibility) {
boolean changed = getVisibility() != visibility;
super.setVisibility(visibility);
if (changed && mControllerCallbacks != null) {
mControllerCallbacks.notifyVisibilityChanged();
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -222,14 +264,14 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
mIconClickListener = mControllerCallbacks.getIconOnClickListener();
mIconLongClickListener = mControllerCallbacks.getIconOnLongClickListener();
setOnLongClickListener(mControllerCallbacks.getBackgroundOnLongClickListener());
if (mAllAppsButton != null) {
mAllAppsButton.setOnClickListener(mControllerCallbacks.getAllAppsButtonClickListener());
}
if (mTaskbarDivider != null) {
mTaskbarDivider.setOnLongClickListener(
mControllerCallbacks.getTaskbarDividerLongClickListener());
mTaskbarDivider.setOnTouchListener(
mControllerCallbacks.getTaskbarDividerRightClickListener());
}
}
@@ -268,12 +310,14 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
// Replace any Hotseat views with the appropriate type if it's not already that type.
final int expectedLayoutResId;
boolean isFolder = false;
boolean isCollection = false;
if (hotseatItemInfo.isPredictedItem()) {
expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
} else if (hotseatItemInfo instanceof FolderInfo) {
expectedLayoutResId = R.layout.folder_icon;
isFolder = true;
} else if (hotseatItemInfo instanceof FolderInfo fi) {
expectedLayoutResId = fi.itemType == ITEM_TYPE_APP_PAIR
? R.layout.app_pair_icon
: R.layout.folder_icon;
isCollection = true;
} else {
expectedLayoutResId = R.layout.taskbar_app_icon;
}
@@ -284,7 +328,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
// see if the view can be reused
if ((hotseatView.getSourceLayoutResId() != expectedLayoutResId)
|| (isFolder && (hotseatView.getTag() != hotseatItemInfo))) {
|| (isCollection && (hotseatView.getTag() != hotseatItemInfo))) {
// Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation,
// so if the info changes we need to reinflate. This should only happen if a new
// folder is dragged to the position that another folder previously existed.
@@ -297,12 +341,23 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
}
if (hotseatView == null) {
if (isFolder) {
if (isCollection) {
FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
mActivityContext, this, folderInfo);
folderIcon.setTextVisible(false);
hotseatView = folderIcon;
switch (hotseatItemInfo.itemType) {
case ITEM_TYPE_FOLDER:
hotseatView = FolderIcon.inflateFolderAndIcon(
expectedLayoutResId, mActivityContext, this, folderInfo);
((FolderIcon) hotseatView).setTextVisible(false);
break;
case ITEM_TYPE_APP_PAIR:
hotseatView = AppPairIcon.inflateIcon(
expectedLayoutResId, mActivityContext, this, folderInfo);
((AppPairIcon) hotseatView).setTextVisible(false);
break;
default:
throw new IllegalStateException(
"Unexpected item type: " + hotseatItemInfo.itemType);
}
} else {
hotseatView = inflate(expectedLayoutResId);
}
@@ -324,7 +379,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
}
}
setClickAndLongClickListenersForIcon(hotseatView);
if (ENABLE_CURSOR_HOVER_STATES.get()) {
if (enableCursorHoverStates()) {
setHoverListenerForIcon(hotseatView);
}
nextViewIndex++;
@@ -335,8 +390,6 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
}
if (mAllAppsButton != null) {
mAllAppsButton.setTranslationXForTaskbarAllAppsIcon(getChildCount() > 0
? mTransientTaskbarAllAppsButtonTranslationXOffset : 0f);
addView(mAllAppsButton, mIsRtl ? getChildCount() : 0);
// if only all apps button present, don't include divider view.
@@ -372,6 +425,16 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
public void setClickAndLongClickListenersForIcon(View icon) {
icon.setOnClickListener(mIconClickListener);
icon.setOnLongClickListener(mIconLongClickListener);
// Add right-click support to btv icons.
icon.setOnTouchListener((v, event) -> {
if (event.isFromSource(InputDevice.SOURCE_MOUSE)
&& (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0
&& v instanceof BubbleTextView) {
mActivityContext.showPopupMenuForIcon((BubbleTextView) v);
return true;
}
return false;
});
}
/**
@@ -464,24 +527,6 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mIconLayoutBounds.left <= event.getX() && event.getX() <= mIconLayoutBounds.right) {
// Don't allow long pressing between icons, or above/below them.
return true;
}
if (mControllerCallbacks.onTouchEvent(event)) {
int oldAction = event.getAction();
try {
event.setAction(MotionEvent.ACTION_CANCEL);
return super.onTouchEvent(event);
} finally {
event.setAction(oldAction);
}
}
return super.onTouchEvent(event);
}
/**
* Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's
* touch bounds.
@@ -509,7 +554,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
int iconLayoutBoundsWidth =
countExcludingQsb * (mItemMarginLeftRight * 2 + mIconTouchSize);
if (FeatureFlags.ENABLE_TASKBAR_PINNING.get() && countExcludingQsb > 1) {
if (enableTaskbarPinning() && 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;
@@ -21,14 +21,19 @@ 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;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.mapRange;
import static com.android.launcher3.anim.AnimatedFloat.VALUE;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT;
import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT;
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;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_PINNING_ANIM;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_REVEAL_ANIM;
import android.animation.Animator;
@@ -38,6 +43,7 @@ import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.graphics.Rect;
import android.util.Log;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
@@ -47,6 +53,7 @@ import androidx.core.graphics.ColorUtils;
import androidx.core.view.OneShotPreDrawListener;
import com.android.app.animation.Interpolators;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
@@ -61,11 +68,13 @@ import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.ThemedIconDrawable;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.views.IconButtonView;
import java.io.PrintWriter;
import java.util.function.Predicate;
@@ -98,12 +107,27 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
this::updateTranslationY);
private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat(
this::updateTranslationY);
private final AnimatedFloat mTaskbarIconScaleForPinning = new AnimatedFloat(
this::updateTaskbarIconsScale);
private final AnimatedFloat mTaskbarIconTranslationXForPinning = new AnimatedFloat(
this::updateTaskbarIconTranslationXForPinning);
private final AnimatedFloat mTaskbarIconTranslationYForPinning = new AnimatedFloat(
this::updateTranslationY);
private final View.OnLayoutChangeListener mTaskbarViewLayoutChangeListener =
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
-> updateTaskbarIconTranslationXForPinning();
private AnimatedFloat mTaskbarNavButtonTranslationY;
private AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay;
private float mTaskbarIconTranslationYForSwipe;
private float mTaskbarIconTranslationYForSpringOnStash;
private final int mTaskbarBottomMargin;
private int mTaskbarBottomMargin;
private final int mStashedHandleHeight;
private final int mLauncherThemedIconsBackgroundColor;
private final int mTaskbarThemedIconsBackgroundColor;
@@ -131,8 +155,18 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
private final boolean mIsRtl;
private final DeviceProfile mTransientTaskbarDp;
private final DeviceProfile mPersistentTaskbarDp;
private final int mTransientIconSize;
private final int mPersistentIconSize;
public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) {
mActivity = activity;
mTransientTaskbarDp = mActivity.getTransientTaskbarDeviceProfile();
mPersistentTaskbarDp = mActivity.getPersistentTaskbarDeviceProfile();
mTransientIconSize = mTransientTaskbarDp.taskbarIconSize;
mPersistentIconSize = mPersistentTaskbarDp.taskbarIconSize;
mTaskbarView = taskbarView;
mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, NUM_ALPHA_CHANNELS);
mTaskbarIconAlpha.setUpdateVisibility(true);
@@ -158,10 +192,16 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
mControllers = controllers;
mTaskbarView.init(new TaskbarViewCallbacks());
mTaskbarView.getLayoutParams().height = isPhoneMode(mActivity.getDeviceProfile())
? mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_size)
? mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_phone_size)
: mActivity.getDeviceProfile().taskbarHeight;
mTaskbarIconScaleForStash.updateValue(1f);
float pinningValue = DisplayController.isTransientTaskbar(mActivity)
? PINNING_TRANSIENT
: PINNING_PERSISTENT;
mTaskbarIconScaleForPinning.updateValue(pinningValue);
mTaskbarIconTranslationYForPinning.updateValue(pinningValue);
mTaskbarIconTranslationXForPinning.updateValue(pinningValue);
mModelCallbacks.init(controllers);
if (mActivity.isUserSetupComplete()) {
@@ -175,12 +215,15 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
// 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();
}
if (enableTaskbarPinning()) {
mTaskbarView.addOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
}
}
/**
@@ -191,6 +234,9 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
}
public void onDestroy() {
if (enableTaskbarPinning()) {
mTaskbarView.removeOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
}
LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
mModelCallbacks.unregisterListeners();
@@ -253,6 +299,18 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
return mTaskbarIconTranslationYForStash;
}
public AnimatedFloat getTaskbarIconScaleForPinning() {
return mTaskbarIconScaleForPinning;
}
public AnimatedFloat getTaskbarIconTranslationXForPinning() {
return mTaskbarIconTranslationXForPinning;
}
public AnimatedFloat getTaskbarIconTranslationYForPinning() {
return mTaskbarIconTranslationYForPinning;
}
/**
* Applies scale properties for the entire TaskbarView (rather than individual icons).
*/
@@ -262,6 +320,80 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
mTaskbarView.setScaleY(scale);
}
/**
* Applies scale properties for the taskbar icons
*/
private void updateTaskbarIconsScale() {
float scale = mTaskbarIconScaleForPinning.value;
View[] iconViews = mTaskbarView.getIconViews();
float finalScale;
if (mControllers.getSharedState().startTaskbarVariantIsTransient) {
finalScale = mapRange(scale, 1f, ((float) mPersistentIconSize / mTransientIconSize));
} else {
finalScale = mapRange(scale, ((float) mTransientIconSize / mPersistentIconSize), 1f);
}
for (int iconIndex = 0; iconIndex < iconViews.length; iconIndex++) {
iconViews[iconIndex].setScaleX(finalScale);
iconViews[iconIndex].setScaleY(finalScale);
}
}
/**
* Animate away taskbar icon notification dots during the taskbar pinning animation.
*/
public void animateAwayNotificationDotsDuringTaskbarPinningAnimation() {
for (View iconView : mTaskbarView.getIconViews()) {
if (iconView instanceof BubbleTextView && ((BubbleTextView) iconView).hasDot()) {
((BubbleTextView) iconView).animateDotScale(0);
}
}
}
private void updateTaskbarIconTranslationXForPinning() {
View[] iconViews = mTaskbarView.getIconViews();
float scale = mTaskbarIconTranslationXForPinning.value;
float taskbarCenterX =
mTaskbarView.getLeft() + (mTaskbarView.getRight() - mTaskbarView.getLeft()) / 2.0f;
float finalMarginScale = mapRange(scale, 0f, mTransientIconSize - mPersistentIconSize);
float transientTaskbarAllAppsOffset = mActivity.getResources().getDimension(
mTaskbarView.getAllAppsButtonTranslationXOffset(true));
float persistentTaskbarAllAppsOffset = mActivity.getResources().getDimension(
mTaskbarView.getAllAppsButtonTranslationXOffset(false));
float allAppIconTranslateRange = mapRange(scale, transientTaskbarAllAppsOffset,
persistentTaskbarAllAppsOffset);
if (mIsRtl) {
allAppIconTranslateRange *= -1;
}
float halfIconCount = iconViews.length / 2.0f;
for (int iconIndex = 0; iconIndex < iconViews.length; iconIndex++) {
View iconView = iconViews[iconIndex];
MultiTranslateDelegate translateDelegate =
((Reorderable) iconView).getTranslateDelegate();
float iconCenterX =
iconView.getLeft() + (iconView.getRight() - iconView.getLeft()) / 2.0f;
if (iconCenterX <= taskbarCenterX) {
translateDelegate.getTranslationX(INDEX_TASKBAR_PINNING_ANIM).setValue(
finalMarginScale * (halfIconCount - iconIndex));
} else {
translateDelegate.getTranslationX(INDEX_TASKBAR_PINNING_ANIM).setValue(
-finalMarginScale * (iconIndex - halfIconCount));
}
if (iconView.equals(mTaskbarView.getAllAppsButtonView()) && iconViews.length > 1) {
((IconButtonView) iconView).setTranslationXForTaskbarAllAppsIcon(
allAppIconTranslateRange);
}
}
}
/**
* Sets the translation of the TaskbarView during the swipe up gesture.
*/
@@ -282,9 +414,40 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
mTaskbarView.setTranslationY(mTaskbarIconTranslationYForHome.value
+ mTaskbarIconTranslationYForStash.value
+ mTaskbarIconTranslationYForSwipe
+ getTaskbarIconTranslationYForPinningValue()
+ mTaskbarIconTranslationYForSpringOnStash);
}
/**
* Computes translation y for taskbar pinning.
*/
private float getTaskbarIconTranslationYForPinningValue() {
if (mControllers.getSharedState() == null) return 0f;
float scale = mTaskbarIconTranslationYForPinning.value;
float taskbarIconTranslationYForPinningValue;
// transY is calculated here by adding/subtracting the taskbar bottom margin
// aligning the icon bound to be at bottom of current taskbar view and then
// finally placing the icon in the middle of new taskbar background height.
if (mControllers.getSharedState().startTaskbarVariantIsTransient) {
float transY =
mTransientTaskbarDp.taskbarBottomMargin + (mTransientTaskbarDp.taskbarHeight
- mTaskbarView.getIconLayoutBounds().bottom)
- (mPersistentTaskbarDp.taskbarHeight
- mTransientTaskbarDp.taskbarIconSize) / 2f;
taskbarIconTranslationYForPinningValue = mapRange(scale, 0f, transY);
} else {
float transY =
-mTransientTaskbarDp.taskbarBottomMargin + (mPersistentTaskbarDp.taskbarHeight
- mTaskbarView.getIconLayoutBounds().bottom)
- (mTransientTaskbarDp.taskbarHeight
- mTransientTaskbarDp.taskbarIconSize) / 2f;
taskbarIconTranslationYForPinningValue = mapRange(scale, transY, 0f);
}
return taskbarIconTranslationYForPinningValue;
}
/**
* Updates the Taskbar's themed icons background according to the progress between in-app/home.
*/
@@ -435,6 +598,11 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
* 1 => fully aligned
*/
public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
if (isPhoneMode(launcherDp)) {
mIconAlignControllerLazy = null;
return;
}
boolean isHotseatIconOnTopWhenAligned =
mControllers.uiController.isHotseatIconOnTopWhenAligned();
boolean isStashed = mControllers.taskbarStashController.isStashed();
@@ -453,19 +621,21 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
}
}
/** Resets the icon alignment controller so that it can be recreated again later. */
void resetIconAlignmentController() {
mIconAlignControllerLazy = null;
}
/**
* Creates an animation for aligning the Taskbar icons with the provided Launcher device profile
*/
private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
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);
boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
float scaleUp = ((float) launcherDp.iconSizePx) / taskbarDp.taskbarIconSize;
int borderSpacing = launcherDp.hotseatBorderSpace;
int hotseatCellSize = DeviceProfile.calculateCellWidth(
@@ -492,6 +662,10 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight(
anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight));
mTaskbarBottomMargin = isTransientTaskbar
? mTransientTaskbarDp.taskbarBottomMargin
: mPersistentTaskbarDp.taskbarBottomMargin;
for (int i = 0; i < mTaskbarView.getChildCount(); i++) {
View child = mTaskbarView.getChildAt(i);
boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonView();
@@ -502,7 +676,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
// to avoid icons disappearing rather than fading out visually.
setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0.8f, 1f));
} else if ((isAllAppsButton && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get())
|| (isTaskbarDividerView && FeatureFlags.ENABLE_TASKBAR_PINNING.get())) {
|| (isTaskbarDividerView && enableTaskbarPinning())) {
if (!isToHome
&& mIsHotseatIconOnTopWhenAligned
&& mIsStashed) {
@@ -523,6 +697,8 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
+ launcherDp.hotseatQsbWidth / 2f
: hotseatPadding.left - borderSpacing - launcherDp.hotseatQsbWidth / 2f;
float childCenter = (child.getLeft() + child.getRight()) / 2f;
childCenter += ((Reorderable) child).getTranslateDelegate().getTranslationX(
INDEX_TASKBAR_PINNING_ANIM).getValue();
float halfQsbIconWidthDiff =
(launcherDp.hotseatQsbWidth - taskbarDp.taskbarIconSize) / 2f;
float scale = ((float) taskbarDp.taskbarIconSize)
@@ -555,13 +731,19 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
continue;
}
int positionInHotseat;
float positionInHotseat;
if (isAllAppsButton) {
// Note that there is no All Apps button in the hotseat, this position is only used
// as its convenient for animation purposes.
// Note that there is no All Apps button in the hotseat,
// this position is only used as its convenient for animation purposes.
positionInHotseat = Utilities.isRtl(child.getResources())
? taskbarDp.numShownHotseatIcons
: -1;
} else if (isTaskbarDividerView) {
// Note that there is no taskbar divider view in the hotseat,
// this position is only used as its convenient for animation purposes.
positionInHotseat = Utilities.isRtl(child.getResources())
? taskbarDp.numShownHotseatIcons - 0.5f
: -0.5f;
} else if (child.getTag() instanceof ItemInfo) {
positionInHotseat = ((ItemInfo) child.getTag()).screenId;
} else {
@@ -569,14 +751,24 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
continue;
}
float hotseatIconCenter = hotseatPadding.left
+ (hotseatCellSize + borderSpacing) * positionInHotseat
+ hotseatCellSize / 2f;
float hotseatAdjustedBorderSpace =
launcherDp.getHotseatAdjustedBorderSpaceForBubbleBar(child.getContext());
float hotseatIconCenter;
if (bubbleBarHasBubbles() && hotseatAdjustedBorderSpace != 0) {
hotseatIconCenter = hotseatPadding.left + hotseatCellSize
+ (hotseatCellSize + hotseatAdjustedBorderSpace) * positionInHotseat
+ hotseatCellSize / 2f;
} else {
hotseatIconCenter = hotseatPadding.left
+ (hotseatCellSize + borderSpacing) * positionInHotseat
+ hotseatCellSize / 2f;
}
float childCenter = (child.getLeft() + child.getRight()) / 2f;
childCenter += ((Reorderable) child).getTranslateDelegate().getTranslationX(
INDEX_TASKBAR_PINNING_ANIM).getValue();
float toX = hotseatIconCenter - childCenter;
if (child instanceof Reorderable) {
MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
setter.setFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM),
MULTI_PROPERTY_VALUE, toX, interpolator);
setter.setFloat(mtd.getTranslationY(INDEX_TASKBAR_ALIGNMENT_ANIM),
@@ -593,6 +785,11 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
return controller;
}
private boolean bubbleBarHasBubbles() {
return mControllers.bubbleControllers.isPresent()
&& mControllers.bubbleControllers.get().bubbleBarViewController.hasBubbles();
}
public void onRotationChanged(DeviceProfile deviceProfile) {
if (!mControllers.uiController.isIconAlignedWithHotseat()) {
// We only translate on rotation when icon is aligned with hotseat
@@ -686,13 +883,19 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
};
}
public View.OnLongClickListener getIconOnLongClickListener() {
return mControllers.taskbarDragController::startDragOnLongClick;
public View.OnTouchListener getTaskbarDividerRightClickListener() {
return (v, event) -> {
if (event.isFromSource(InputDevice.SOURCE_MOUSE)
&& event.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
mControllers.taskbarPinningController.showPinningView(v);
return true;
}
return false;
};
}
public View.OnLongClickListener getBackgroundOnLongClickListener() {
return view -> mControllers.taskbarStashController
.updateAndAnimateIsManuallyStashedInApp(true);
public View.OnLongClickListener getIconOnLongClickListener() {
return mControllers.taskbarDragController::startDragOnLongClick;
}
/** Gets the hover listener for the provided icon view. */
@@ -700,47 +903,19 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
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.
* TODO(b/270395798): We can remove this entirely once we remove the Transient Taskbar flag.
*/
public boolean onTouchEvent(MotionEvent motionEvent) {
final float x = motionEvent.getRawX();
final float y = motionEvent.getRawY();
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = x;
mDownY = y;
mControllers.taskbarStashController.startStashHint(/* animateForward = */ true);
mCanceledStashHint = false;
break;
case MotionEvent.ACTION_MOVE:
if (!mCanceledStashHint
&& squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop) {
mControllers.taskbarStashController.startStashHint(
/* animateForward= */ false);
mCanceledStashHint = true;
return true;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (!mCanceledStashHint) {
mControllers.taskbarStashController.startStashHint(
/* animateForward= */ false);
}
break;
}
return false;
}
/**
* Notifies launcher to update icon alignment.
*/
public void notifyIconLayoutBoundsChanged() {
mControllers.uiController.onIconLayoutBoundsChanged();
}
/**
* Notifies the taskbar scrim when the visibility of taskbar changes.
*/
public void notifyVisibilityChanged() {
mControllers.taskbarScrimViewController.onTaskbarVisibilityChanged(
mTaskbarView.getVisibility());
}
}
}
@@ -89,7 +89,8 @@ public final class TaskbarAllAppsController {
mAppsModelFlags = flags;
mPackageUserKeytoUidMap = map;
if (mAppsView != null) {
mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap);
mAppsView.getAppsStore().setApps(
mApps, mAppsModelFlags, mPackageUserKeytoUidMap, false);
}
}
@@ -128,10 +129,19 @@ public final class TaskbarAllAppsController {
/** Toggles visibility of {@link TaskbarAllAppsContainerView} in the overlay window. */
public void toggle() {
toggle(false);
}
/** Toggles visibility of {@link TaskbarAllAppsContainerView} with the keyboard for search. */
public void toggleSearch() {
toggle(true);
}
private void toggle(boolean showKeyboard) {
if (isOpen()) {
mSlideInView.close(true);
} else {
show(true);
show(true, showKeyboard);
}
}
@@ -141,13 +151,13 @@ public final class TaskbarAllAppsController {
}
private void show(boolean animate) {
show(animate, false);
}
private void show(boolean animate, boolean showKeyboard) {
if (mAppsView != null) {
return;
}
// mControllers and getSharedState should never be null here. Do not handle null-pointer
// to catch invalid states.
mControllers.getSharedState().allAppsVisible = true;
mOverlayContext = mControllers.taskbarOverlayController.requestWindow();
// Initialize search session for All Apps.
@@ -161,16 +171,20 @@ public final class TaskbarAllAppsController {
mSlideInView = (TaskbarAllAppsSlideInView) mOverlayContext.getLayoutInflater().inflate(
R.layout.taskbar_all_apps_sheet, mOverlayContext.getDragLayer(), false);
mSlideInView.addOnCloseListener(() -> {
mControllers.getSharedState().allAppsVisible = false;
cleanUpOverlay();
});
// Ensures All Apps gets touch events in case it is not the top floating view. Floating
// views above it may not be able to intercept the touch, so All Apps should try to.
mOverlayContext.getDragLayer().addTouchController(mSlideInView);
mSlideInView.addOnCloseListener(this::cleanUpOverlay);
TaskbarAllAppsViewController viewController = new TaskbarAllAppsViewController(
mOverlayContext, mSlideInView, mControllers, mSearchSessionController);
mOverlayContext,
mSlideInView,
mControllers,
mSearchSessionController,
showKeyboard);
viewController.show(animate);
mAppsView = mOverlayContext.getAppsView();
mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap);
mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap, false);
mAppsView.getFloatingHeaderView()
.findFixedRowByType(PredictionRowView.class)
.setPredictedApps(mPredictedApps);
@@ -195,6 +209,7 @@ public final class TaskbarAllAppsController {
mSearchSessionController = null;
}
if (mOverlayContext != null) {
mOverlayContext.getDragLayer().removeTouchController(mSlideInView);
mOverlayContext.setSearchSessionController(null);
mOverlayContext = null;
}
@@ -220,7 +220,9 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = !mAppsView.shouldContainerScroll(ev);
mNoIntercept = !mAppsView.shouldContainerScroll(ev)
|| getTopOpenViewWithType(
mActivityContext, TYPE_TOUCH_CONTROLLER_NO_INTERCEPT) != null;
}
return super.onControllerInterceptTouchEvent(ev);
}
@@ -18,17 +18,22 @@ package com.android.launcher3.taskbar.allapps;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_TASKBAR_ALL_APPS;
import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
import androidx.annotation.Nullable;
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.taskbar.NavbarButtonsViewController;
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.taskbar.TaskbarSharedState;
import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
import com.android.launcher3.util.DisplayController;
import java.util.Optional;
/**
* Handles the {@link TaskbarAllAppsContainerView} behavior and synchronizes its transitions with
* taskbar stashing.
@@ -41,12 +46,15 @@ final class TaskbarAllAppsViewController {
private final TaskbarStashController mTaskbarStashController;
private final NavbarButtonsViewController mNavbarButtonsViewController;
private final TaskbarOverlayController mOverlayController;
private final @Nullable TaskbarSharedState mTaskbarSharedState;
private final boolean mShowKeyboard;
TaskbarAllAppsViewController(
TaskbarOverlayContext context,
TaskbarAllAppsSlideInView slideInView,
TaskbarControllers taskbarControllers,
TaskbarSearchSessionController searchSessionController) {
TaskbarSearchSessionController searchSessionController,
boolean showKeyboard) {
mContext = context;
mSlideInView = slideInView;
@@ -54,6 +62,8 @@ final class TaskbarAllAppsViewController {
mTaskbarStashController = taskbarControllers.taskbarStashController;
mNavbarButtonsViewController = taskbarControllers.navbarButtonsViewController;
mOverlayController = taskbarControllers.taskbarOverlayController;
mTaskbarSharedState = taskbarControllers.getSharedState();
mShowKeyboard = showKeyboard;
mSlideInView.init(new TaskbarAllAppsCallbacks(searchSessionController));
setUpAppDivider();
@@ -73,26 +83,27 @@ final class TaskbarAllAppsViewController {
private void setUpAppDivider() {
mAppsView.getFloatingHeaderView()
.findFixedRowByType(AppsDividerView.class)
.setShowAllAppsLabel(!mContext.getOnboardingPrefs().hasReachedMaxCount(
ALL_APPS_VISITED_COUNT));
mContext.getOnboardingPrefs().incrementEventCount(ALL_APPS_VISITED_COUNT);
.setShowAllAppsLabel(!ALL_APPS_VISITED_COUNT.hasReachedMax(mContext));
ALL_APPS_VISITED_COUNT.increment(mContext);
}
private void setUpTaskbarStashing() {
if (DisplayController.isTransientTaskbar(mContext)) {
mTaskbarStashController.updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, true);
mTaskbarStashController.applyState(mOverlayController.getOpenDuration());
mTaskbarStashController.applyState();
}
Optional.ofNullable(mTaskbarSharedState).ifPresent(s -> s.allAppsVisible = true);
mNavbarButtonsViewController.setSlideInViewVisible(true);
mSlideInView.setOnCloseBeginListener(() -> {
Optional.ofNullable(mTaskbarSharedState).ifPresent(s -> s.allAppsVisible = false);
mNavbarButtonsViewController.setSlideInViewVisible(false);
AbstractFloatingView.closeOpenContainer(
mContext, AbstractFloatingView.TYPE_ACTION_POPUP);
if (DisplayController.isTransientTaskbar(mContext)) {
mTaskbarStashController.updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, false);
mTaskbarStashController.applyState(mOverlayController.getCloseDuration());
mTaskbarStashController.applyState();
}
});
}
@@ -120,6 +131,11 @@ final class TaskbarAllAppsViewController {
@Override
public void onAllAppsTransitionEnd(boolean toAllApps) {
mSearchSessionController.onAllAppsTransitionEnd(toAllApps);
if (toAllApps
&& mShowKeyboard
&& mAppsView.getSearchUiManager().getEditText() != null) {
mAppsView.getSearchUiManager().getEditText().requestFocus();
}
}
/** Invoked on back press, returning {@code true} if the search session handled it. */
@@ -128,7 +144,7 @@ final class TaskbarAllAppsViewController {
}
void onAllAppsAnimationPending(PendingAnimation animation, boolean toAllApps) {
mSearchSessionController.onAllAppsAnimationPending(animation, toAllApps);
mSearchSessionController.onAllAppsAnimationPending(animation, toAllApps, mShowKeyboard);
}
}
}
@@ -51,7 +51,11 @@ open class TaskbarSearchSessionController : ResourceBasedOverride, AllAppsTransi
open fun handleBackInvoked(): Boolean = false
open fun onAllAppsAnimationPending(animation: PendingAnimation, toAllApps: Boolean) = Unit
open fun onAllAppsAnimationPending(
animation: PendingAnimation,
toAllApps: Boolean,
showKeyboard: Boolean,
) = Unit
companion object {
@JvmStatic
@@ -85,17 +85,31 @@ import java.util.concurrent.Executors;
* information to render each of the bubbles & dispatches changes to
* {@link BubbleBarViewController} which will then update {@link BubbleBarView} as needed.
*
* For details around the behavior of the bubble bar, see {@link BubbleBarView}.
* <p>For details around the behavior of the bubble bar, see {@link BubbleBarView}.
*/
public class BubbleBarController extends IBubblesListener.Stub {
private static final String TAG = BubbleBarController.class.getSimpleName();
private static final boolean DEBUG = false;
// Whether bubbles are showing in the bubble bar from launcher
public static final boolean BUBBLE_BAR_ENABLED =
/**
* Determines whether bubbles can be shown in the bubble bar. This value updates when the
* taskbar is recreated.
*
* @see #onTaskbarRecreated()
*/
private static boolean sBubbleBarEnabled =
SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
/** Whether showing bubbles in the launcher bubble bar is enabled. */
public static boolean isBubbleBarEnabled() {
return sBubbleBarEnabled;
}
/** Re-reads the value of the flag from SystemProperties when taskbar is recreated. */
public static void onTaskbarRecreated() {
sBubbleBarEnabled = 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
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
@@ -137,6 +151,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
private static class BubbleBarViewUpdate {
boolean expandedChanged;
boolean expanded;
boolean shouldShowEducation;
String selectedBubbleKey;
String suppressedBubbleKey;
String unsuppressedBubbleKey;
@@ -151,6 +166,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
BubbleBarViewUpdate(BubbleBarUpdate update) {
expandedChanged = update.expandedChanged;
expanded = update.expanded;
shouldShowEducation = update.shouldShowEducation;
selectedBubbleKey = update.selectedBubbleKey;
suppressedBubbleKey = update.suppressedBubbleKey;
unsuppressedBubbleKey = update.unsupressedBubbleKey;
@@ -165,7 +181,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
mSystemUiProxy = SystemUiProxy.INSTANCE.get(context);
if (BUBBLE_BAR_ENABLED) {
if (sBubbleBarEnabled) {
mSystemUiProxy.setBubblesListener(this);
}
mMainExecutor = MAIN_EXECUTOR;
@@ -188,8 +204,10 @@ public class BubbleBarController extends IBubblesListener.Stub {
mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
bubbleControllers.runAfterInit(() -> {
mBubbleBarViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
mBubbleStashedHandleViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
mBubbleBarViewController.setHiddenForBubbles(
!sBubbleBarEnabled || mBubbles.isEmpty());
mBubbleStashedHandleViewController.setHiddenForBubbles(
!sBubbleBarEnabled || mBubbles.isEmpty());
mBubbleBarViewController.setUpdateSelectedBubbleAfterCollapse(
key -> setSelectedBubble(mBubbles.get(key)));
});
@@ -366,7 +384,9 @@ public class BubbleBarController extends IBubblesListener.Stub {
mBubbleStashController.animateToInitialState(update.expanded);
}
}
if (update.shouldShowEducation) {
mBubbleBarViewController.prepareToShowEducation();
}
if (update.expandedChanged) {
if (update.expanded != mBubbleBarViewController.isExpanded()) {
mBubbleBarViewController.setExpandedFromSysui(update.expanded);
@@ -18,6 +18,7 @@ package com.android.launcher3.taskbar.bubbles;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Log;
import android.view.MotionEvent;
@@ -74,7 +75,8 @@ public class BubbleBarViewController {
// Whether the bar is hidden for a sysui state.
private boolean mHiddenForSysui;
// Whether the bar is hidden because there are no bubbles.
private boolean mHiddenForNoBubbles;
private boolean mHiddenForNoBubbles = true;
private boolean mShouldShowEducation;
public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView) {
mActivity = activity;
@@ -98,7 +100,7 @@ public class BubbleBarViewController {
mBarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarHeight;
mBubbleBarScale.updateValue(1f);
mBubbleClickListener = v -> onBubbleClicked(v);
mBubbleBarClickListener = v -> setExpanded(true);
mBubbleBarClickListener = v -> onBubbleBarClicked();
mBubbleDragController.setupBubbleBarView(mBarView);
mBarView.setOnClickListener(mBubbleBarClickListener);
mBarView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) ->
@@ -121,6 +123,21 @@ public class BubbleBarViewController {
}
}
private void onBubbleBarClicked() {
if (mShouldShowEducation) {
mShouldShowEducation = false;
// Get the bubble bar bounds on screen
Rect bounds = new Rect();
mBarView.getBoundsOnScreen(bounds);
// Calculate user education reference position in Screen coordinates
Point position = new Point(bounds.centerX(), bounds.top);
// Show user education relative to the reference point
mSystemUiProxy.showUserEducation(position);
} else {
setExpanded(true);
}
}
//
// The below animators are exposed to BubbleStashController so it can manage the stashing
// animation.
@@ -195,6 +212,7 @@ public class BubbleBarViewController {
if (mHiddenForNoBubbles != hidden) {
mHiddenForNoBubbles = hidden;
updateVisibilityForStateChange();
mActivity.bubbleBarVisibilityChanged(!hidden);
}
}
@@ -326,6 +344,12 @@ public class BubbleBarViewController {
}
}
/** Marks as should show education and shows the bubble bar in a collapsed state */
public void prepareToShowEducation() {
mShouldShowEducation = true;
mBubbleStashController.showBubbleBar(false /* expand the bubbles */);
}
/**
* Updates the dragged bubble view in the bubble bar view, and notifies SystemUI
* that a bubble is being dragged to dismiss.
@@ -30,6 +30,7 @@ import com.android.wm.shell.common.bubbles.DismissView
fun DismissView.setup() {
setup(
DismissView.Config(
dismissViewResId = R.id.dismiss_view,
targetSizeResId = R.dimen.bubblebar_dismiss_target_size,
iconSizeResId = R.dimen.bubblebar_dismiss_target_icon_size,
bottomMarginResId = R.dimen.bubblebar_dismiss_target_bottom_margin,
@@ -22,6 +22,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.annotation.Nullable;
import android.view.InsetsController;
import android.view.MotionEvent;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.taskbar.StashedHandleViewController;
@@ -350,4 +351,9 @@ public class BubbleStashController {
return mBubblesShowingOnHome ? getBubbleBarTranslationYForHotseat()
: getBubbleBarTranslationYForTaskbar();
}
/** Checks whether the motion event is over the stash handle. */
public boolean isEventOverStashHandle(MotionEvent ev) {
return mHandleViewController.isEventOverHandle(ev);
}
}
@@ -24,6 +24,7 @@ import android.animation.ValueAnimator;
import android.content.res.Resources;
import android.graphics.Outline;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewOutlineProvider;
@@ -268,4 +269,22 @@ public class BubbleStashedHandleViewController {
});
return revealAnim;
}
/** Checks that the stash handle is visible and that the motion event is within bounds. */
public boolean isEventOverHandle(MotionEvent ev) {
if (mStashedHandleView.getVisibility() != VISIBLE) {
return false;
}
// the bounds of the handle only include the visible part, so we check that the Y coordinate
// is anywhere within the stashed taskbar height.
int top = mActivity.getDeviceProfile().heightPx - mStashedTaskbarHeight;
return (int) ev.getRawY() >= top && containsX((int) ev.getRawX());
}
/** Checks if the given x coordinate is within the stashed handle bounds. */
public boolean containsX(int x) {
return x >= mStashedHandleBounds.left && x <= mStashedHandleBounds.right;
}
}
@@ -163,6 +163,7 @@ public class BubbleView extends ConstraintLayout {
mBubble = overflow;
mBubbleIcon.setImageBitmap(bitmap);
mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
setContentDescription(getResources().getString(R.string.bubble_bar_overflow_description));
}
/** Returns the bubble being rendered in this view. */
@@ -18,12 +18,15 @@ package com.android.launcher3.taskbar.navbutton
import android.content.res.Resources
import android.graphics.drawable.RotateDrawable
import android.view.Gravity
import android.view.ViewGroup
import android.widget.FrameLayout
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
import com.android.systemui.shared.rotation.RotationButton
/**
* Meant to be a simple container for data subclasses will need
@@ -37,10 +40,13 @@ import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonL
* @property startContextualContainer ViewGroup that holds the start contextual button (ex, A11y).
*/
abstract class AbstractNavButtonLayoutter(
val resources: Resources,
val navButtonContainer: LinearLayout,
protected val endContextualContainer: ViewGroup,
protected val startContextualContainer: ViewGroup
val resources: Resources,
val navButtonContainer: LinearLayout,
protected val endContextualContainer: ViewGroup,
protected val startContextualContainer: ViewGroup,
protected val imeSwitcher: ImageView?,
protected val rotationButton: RotationButton?,
protected val a11yButton: ImageView?
) : NavButtonLayoutter {
protected val homeButton: ImageView? = navButtonContainer.findViewById(R.id.home)
protected val recentsButton: ImageView? = navButtonContainer.findViewById(R.id.recent_apps)
@@ -56,4 +62,25 @@ abstract class AbstractNavButtonLayoutter(
backButton.setImageDrawable(rotateDrawable)
}
}
fun getParamsToCenterView(): FrameLayout.LayoutParams {
val params = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
params.gravity = Gravity.CENTER
return params;
}
open fun repositionContextualContainer(contextualContainer: ViewGroup, barAxisMargin: Int,
gravity: Int) {
val contextualContainerParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
contextualContainerParams.apply {
marginStart = barAxisMargin
marginEnd = barAxisMargin
topMargin = 0
bottomMargin = 0
}
contextualContainerParams.gravity = gravity or Gravity.CENTER_VERTICAL
contextualContainer.layoutParams = contextualContainerParams
}
}
@@ -25,22 +25,30 @@ import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.*
import com.android.systemui.shared.rotation.RotationButton
class KidsNavLayoutter(
resources: Resources,
navBarContainer: LinearLayout,
endContextualContainer: ViewGroup,
startContextualContainer: ViewGroup
resources: Resources,
navBarContainer: LinearLayout,
endContextualContainer: ViewGroup,
startContextualContainer: ViewGroup,
imeSwitcher: ImageView?,
rotationButton: RotationButton?,
a11yButton: ImageView?
) :
AbstractNavButtonLayoutter(
resources,
navBarContainer,
endContextualContainer,
startContextualContainer
resources,
navBarContainer,
endContextualContainer,
startContextualContainer,
imeSwitcher,
rotationButton,
a11yButton
) {
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
val iconSize: Int = resources.getDimensionPixelSize(DIMEN_TASKBAR_ICON_SIZE_KIDS)
val buttonWidth: Int = resources.getDimensionPixelSize(DIMEN_TASKBAR_NAV_BUTTONS_WIDTH_KIDS)
val buttonHeight: Int =
@@ -89,5 +97,25 @@ class KidsNavLayoutter(
navButtonContainer.requestLayout()
homeButton.onLongClickListener = null
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
val contextualMargin = resources.getDimensionPixelSize(
R.dimen.taskbar_contextual_button_padding)
repositionContextualContainer(endContextualContainer, 0, Gravity.END)
repositionContextualContainer(startContextualContainer, contextualMargin, Gravity.START)
if (imeSwitcher != null) {
startContextualContainer.addView(imeSwitcher)
imeSwitcher.layoutParams = getParamsToCenterView()
}
if (a11yButton != null) {
endContextualContainer.addView(a11yButton)
}
if (rotationButton != null) {
endContextualContainer.addView(rotationButton.currentView)
rotationButton.currentView.layoutParams = getParamsToCenterView()
}
}
}
@@ -21,11 +21,13 @@ import android.view.Surface.ROTATION_90
import android.view.Surface.Rotation
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.launcher3.DeviceProfile
import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.*
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.Companion
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
import com.android.systemui.shared.rotation.RotationButton
/**
* Select the correct layout for nav buttons
@@ -52,14 +54,17 @@ class NavButtonLayoutFactory {
* @param isThreeButtonNav are no-ops when taskbar is present/showing
*/
fun getUiLayoutter(
deviceProfile: DeviceProfile,
navButtonsView: FrameLayout,
resources: Resources,
isKidsMode: Boolean,
isInSetup: Boolean,
isThreeButtonNav: Boolean,
phoneMode: Boolean,
@Rotation surfaceRotation: Int
deviceProfile: DeviceProfile,
navButtonsView: FrameLayout,
imeSwitcher: ImageView?,
rotationButton: RotationButton?,
a11yButton: ImageView?,
resources: Resources,
isKidsMode: Boolean,
isInSetup: Boolean,
isThreeButtonNav: Boolean,
phoneMode: Boolean,
@Rotation surfaceRotation: Int
): NavButtonLayoutter {
val navButtonContainer =
navButtonsView.requireViewById<LinearLayout>(ID_END_NAV_BUTTONS)
@@ -73,24 +78,33 @@ class NavButtonLayoutFactory {
isPhoneNavMode -> {
if (!deviceProfile.isLandscape) {
PhonePortraitNavLayoutter(
resources,
navButtonContainer,
endContextualContainer,
startContextualContainer
resources,
navButtonContainer,
endContextualContainer,
startContextualContainer,
imeSwitcher,
rotationButton,
a11yButton
)
} else if (surfaceRotation == ROTATION_90) {
PhoneLandscapeNavLayoutter(
resources,
navButtonContainer,
endContextualContainer,
startContextualContainer
resources,
navButtonContainer,
endContextualContainer,
startContextualContainer,
imeSwitcher,
rotationButton,
a11yButton
)
} else {
PhoneSeascapeNavLayoutter(
resources,
navButtonContainer,
endContextualContainer,
startContextualContainer
startContextualContainer,
imeSwitcher,
rotationButton,
a11yButton
)
}
}
@@ -99,33 +113,45 @@ class NavButtonLayoutFactory {
resources,
navButtonContainer,
endContextualContainer,
startContextualContainer
startContextualContainer,
imeSwitcher,
rotationButton,
a11yButton
)
}
deviceProfile.isTaskbarPresent -> {
return when {
isInSetup -> {
SetupNavLayoutter(
resources,
navButtonContainer,
endContextualContainer,
startContextualContainer
resources,
navButtonContainer,
endContextualContainer,
startContextualContainer,
imeSwitcher,
rotationButton,
a11yButton
)
}
isKidsMode -> {
KidsNavLayoutter(
resources,
navButtonContainer,
endContextualContainer,
startContextualContainer
resources,
navButtonContainer,
endContextualContainer,
startContextualContainer,
imeSwitcher,
rotationButton,
a11yButton
)
}
else ->
TaskbarNavLayoutter(
resources,
navButtonContainer,
endContextualContainer,
startContextualContainer
resources,
navButtonContainer,
endContextualContainer,
startContextualContainer,
imeSwitcher,
rotationButton,
a11yButton
)
}
}
@@ -136,6 +162,6 @@ class NavButtonLayoutFactory {
/** Lays out and provides access to the home, recents, and back buttons for various mischief */
interface NavButtonLayoutter {
fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean)
fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean)
}
}
@@ -18,24 +18,33 @@ package com.android.launcher3.taskbar.navbutton
import android.content.res.Resources
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.launcher3.DeviceProfile
import com.android.systemui.shared.rotation.RotationButton
/** Layoutter for showing gesture navigation on phone screen. No buttons here, no-op container */
class PhoneGestureLayoutter(
resources: Resources,
navBarContainer: LinearLayout,
endContextualContainer: ViewGroup,
startContextualContainer: ViewGroup
startContextualContainer: ViewGroup,
imeSwitcher: ImageView?,
rotationButton: RotationButton?,
a11yButton: ImageView?
) :
AbstractNavButtonLayoutter(
resources,
navBarContainer,
endContextualContainer,
startContextualContainer
startContextualContainer,
imeSwitcher,
rotationButton,
a11yButton
) {
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
// no-op
override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
}
}
@@ -20,27 +20,35 @@ import android.content.res.Resources
import android.view.Gravity
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.core.view.children
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarManager
import com.android.launcher3.util.DimensionUtils
import com.android.systemui.shared.rotation.RotationButton
open class PhoneLandscapeNavLayoutter(
resources: Resources,
navBarContainer: LinearLayout,
endContextualContainer: ViewGroup,
startContextualContainer: ViewGroup
resources: Resources,
navBarContainer: LinearLayout,
endContextualContainer: ViewGroup,
startContextualContainer: ViewGroup,
imeSwitcher: ImageView?,
rotationButton: RotationButton?,
a11yButton: ImageView?,
) :
AbstractNavButtonLayoutter(
resources,
navBarContainer,
endContextualContainer,
startContextualContainer
resources,
navBarContainer,
endContextualContainer,
startContextualContainer,
imeSwitcher,
rotationButton,
a11yButton
) {
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
// TODO(b/230395757): Polish pending, this is just to make it usable
val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
val taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
@@ -57,14 +65,11 @@ open class PhoneLandscapeNavLayoutter(
marginStart = 0
}
// Swap recents and back button
navButtonContainer.addView(recentsButton)
navButtonContainer.addView(homeButton)
navButtonContainer.addView(backButton)
navButtonContainer.layoutParams = navContainerParams
navButtonContainer.gravity = Gravity.CENTER
addThreeButtons()
// Add the spaces in between the nav buttons
val spaceInBetween: Int =
resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone)
@@ -84,5 +89,49 @@ open class PhoneLandscapeNavLayoutter(
}
}
}
repositionContextualButtons()
}
open fun addThreeButtons() {
// Swap recents and back button
navButtonContainer.addView(recentsButton)
navButtonContainer.addView(homeButton)
navButtonContainer.addView(backButton)
}
open fun repositionContextualButtons() {
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
val contextualMargin = resources.getDimensionPixelSize(
R.dimen.taskbar_contextual_button_padding)
repositionContextualContainer(startContextualContainer, contextualMargin, Gravity.TOP)
if (imeSwitcher != null) {
startContextualContainer.addView(imeSwitcher)
imeSwitcher.layoutParams = getParamsToCenterView()
}
if (a11yButton != null) {
startContextualContainer.addView(a11yButton)
}
if (rotationButton != null) {
startContextualContainer.addView(rotationButton.currentView)
rotationButton.currentView.layoutParams = getParamsToCenterView()
}
}
override fun repositionContextualContainer(contextualContainer: ViewGroup, barAxisMargin: Int,
gravity: Int) {
val contextualContainerParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
contextualContainerParams.apply {
marginStart = 0
marginEnd = 0
topMargin = barAxisMargin
bottomMargin = barAxisMargin
}
contextualContainerParams.gravity = gravity or Gravity.CENTER_HORIZONTAL
contextualContainer.layoutParams = contextualContainerParams
}
}
@@ -20,26 +20,34 @@ import android.content.res.Resources
import android.view.Gravity
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarManager
import com.android.launcher3.util.DimensionUtils
import com.android.systemui.shared.rotation.RotationButton
class PhonePortraitNavLayoutter(
resources: Resources,
navBarContainer: LinearLayout,
endContextualContainer: ViewGroup,
startContextualContainer: ViewGroup
resources: Resources,
navBarContainer: LinearLayout,
endContextualContainer: ViewGroup,
startContextualContainer: ViewGroup,
imeSwitcher: ImageView?,
rotationButton: RotationButton?,
a11yButton: ImageView?,
) :
AbstractNavButtonLayoutter(
resources,
navBarContainer,
endContextualContainer,
startContextualContainer
resources,
navBarContainer,
endContextualContainer,
startContextualContainer,
imeSwitcher,
rotationButton,
a11yButton
) {
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
// TODO(b/230395757): Polish pending, this is just to make it usable
val taskbarDimensions =
DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
@@ -90,5 +98,24 @@ class PhonePortraitNavLayoutter(
}
}
}
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
val contextualMargin = resources.getDimensionPixelSize(
R.dimen.taskbar_contextual_button_padding)
repositionContextualContainer(endContextualContainer, contextualMargin, Gravity.END)
if (imeSwitcher != null) {
endContextualContainer.addView(imeSwitcher)
imeSwitcher.layoutParams = getParamsToCenterView()
}
if (a11yButton != null) {
endContextualContainer.addView(a11yButton)
}
if (rotationButton != null) {
endContextualContainer.addView(rotationButton.currentView)
rotationButton.currentView.layoutParams = getParamsToCenterView()
}
}
}
@@ -17,30 +17,57 @@
package com.android.launcher3.taskbar.navbutton
import android.content.res.Resources
import android.view.Gravity
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.systemui.shared.rotation.RotationButton
class PhoneSeascapeNavLayoutter(
resources: Resources,
navBarContainer: LinearLayout,
endContextualContainer: ViewGroup,
startContextualContainer: ViewGroup
startContextualContainer: ViewGroup,
imeSwitcher: ImageView?,
rotationButton: RotationButton?,
a11yButton: ImageView?
) :
PhoneLandscapeNavLayoutter(
resources,
navBarContainer,
endContextualContainer,
startContextualContainer
startContextualContainer,
imeSwitcher,
rotationButton,
a11yButton
) {
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()
override fun addThreeButtons() {
// Flip ordering of back and recents buttons
navButtonContainer.addView(backButton)
navButtonContainer.addView(homeButton)
navButtonContainer.addView(recentsButton)
}
override fun repositionContextualButtons() {
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
val contextualMargin = resources.getDimensionPixelSize(
R.dimen.taskbar_contextual_button_padding)
repositionContextualContainer(endContextualContainer, contextualMargin, Gravity.BOTTOM)
if (imeSwitcher != null) {
endContextualContainer.addView(imeSwitcher)
imeSwitcher.layoutParams = getParamsToCenterView()
}
if (a11yButton != null) {
endContextualContainer.addView(a11yButton)
}
if (rotationButton != null) {
endContextualContainer.addView(rotationButton.currentView)
rotationButton.currentView.layoutParams = getParamsToCenterView()
}
}
}
@@ -20,23 +20,32 @@ import android.content.res.Resources
import android.view.Gravity
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.systemui.shared.rotation.RotationButton
class SetupNavLayoutter(
resources: Resources,
navButtonContainer: LinearLayout,
endContextualContainer: ViewGroup,
startContextualContainer: ViewGroup
resources: Resources,
navButtonContainer: LinearLayout,
endContextualContainer: ViewGroup,
startContextualContainer: ViewGroup,
imeSwitcher: ImageView?,
rotationButton: RotationButton?,
a11yButton: ImageView?
) :
AbstractNavButtonLayoutter(
resources,
navButtonContainer,
endContextualContainer,
startContextualContainer
resources,
navButtonContainer,
endContextualContainer,
startContextualContainer,
imeSwitcher,
rotationButton,
a11yButton
) {
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
// Since setup wizard only has back button enabled, it looks strange to be
// end-aligned, so start-align instead.
val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
@@ -46,5 +55,25 @@ class SetupNavLayoutter(
gravity = Gravity.START
}
navButtonContainer.requestLayout()
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
val contextualMargin = resources.getDimensionPixelSize(
R.dimen.taskbar_contextual_button_padding)
repositionContextualContainer(endContextualContainer, 0, Gravity.END)
repositionContextualContainer(startContextualContainer, contextualMargin, Gravity.START)
if (imeSwitcher != null) {
startContextualContainer.addView(imeSwitcher)
imeSwitcher.layoutParams = getParamsToCenterView()
}
if (a11yButton != null) {
endContextualContainer.addView(a11yButton)
}
if (rotationButton != null) {
endContextualContainer.addView(rotationButton.currentView)
rotationButton.currentView.layoutParams = getParamsToCenterView()
}
}
}
@@ -20,31 +20,41 @@ import android.content.res.Resources
import android.view.Gravity
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.systemui.shared.rotation.RotationButton
/** Layoutter for showing 3 button navigation on large screen */
/**
* Layoutter for rendering task bar in large screen, both in 3-button and gesture nav mode.
*/
class TaskbarNavLayoutter(
resources: Resources,
navBarContainer: LinearLayout,
endContextualContainer: ViewGroup,
startContextualContainer: ViewGroup
resources: Resources,
navBarContainer: LinearLayout,
endContextualContainer: ViewGroup,
startContextualContainer: ViewGroup,
imeSwitcher: ImageView?,
rotationButton: RotationButton?,
a11yButton: ImageView?
) :
AbstractNavButtonLayoutter(
resources,
navBarContainer,
endContextualContainer,
startContextualContainer
resources,
navBarContainer,
endContextualContainer,
startContextualContainer,
imeSwitcher,
rotationButton,
a11yButton
) {
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
// Add spacing after the end of the last nav button
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
// contextual button to be showing - if not, move the nav buttons over a smidge
if (isContextualButtonShowing && navMarginEnd < contextualWidth) {
if (isA11yButtonPersistent && navMarginEnd < contextualWidth) {
// Additional spacing, eat up half of space between last icon and nav button
navMarginEnd += resources.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2
}
@@ -77,5 +87,27 @@ class TaskbarNavLayoutter(
}
}
}
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
if (!dp.isGestureMode) {
val contextualMargin = resources.getDimensionPixelSize(
R.dimen.taskbar_contextual_button_padding)
repositionContextualContainer(endContextualContainer, 0, Gravity.END)
repositionContextualContainer(startContextualContainer, contextualMargin, Gravity.START)
if (imeSwitcher != null) {
startContextualContainer.addView(imeSwitcher)
imeSwitcher.layoutParams = getParamsToCenterView()
}
if (a11yButton != null) {
endContextualContainer.addView(a11yButton)
}
if (rotationButton != null) {
endContextualContainer.addView(rotationButton.currentView)
rotationButton.currentView.layoutParams = getParamsToCenterView()
}
}
}
}
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar.overlay;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
@@ -23,7 +24,6 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.LauncherState.ALL_APPS;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.PixelFormat;
import android.view.Gravity;
@@ -60,14 +60,26 @@ public final class TaskbarOverlayController {
private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onTaskCreated(int taskId, ComponentName componentName) {
// Created task will be below existing overlay, so move out of the way.
hideWindow();
public void onTaskMovedToFront(int taskId) {
// New front task will be below existing overlay, so move out of the way.
hideWindowOnTaskStackChange();
}
@Override
public void onTaskMovedToFront(int taskId) {
// New front task will be below existing overlay, so move out of the way.
public void onTaskStackChanged() {
// The other callbacks are insufficient for All Apps, because there are many cases where
// it can relaunch the same task already behind it. However, this callback needs to be a
// no-op when only EDU is shown, because going between the EDU steps invokes this
// callback.
if (mControllers.getSharedState() != null
&& mControllers.getSharedState().allAppsVisible) {
hideWindowOnTaskStackChange();
}
}
private void hideWindowOnTaskStackChange() {
// A task was launched while overlay window was open, so stash Taskbar.
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
hideWindow();
}
};
@@ -176,6 +188,7 @@ public final class TaskbarOverlayController {
layoutParams.setFitInsetsTypes(0); // Handled by container view.
layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
layoutParams.setSystemApplicationOverlay(true);
layoutParams.privateFlags = PRIVATE_FLAG_CONSUME_IME_INSETS;
return layoutParams;
}
@@ -199,8 +212,10 @@ public final class TaskbarOverlayController {
@Override
protected void handleClose(boolean animate) {
mTaskbarContext.getDragLayer().removeView(this);
Optional.ofNullable(mOverlayContext).ifPresent(c -> closeAllOpenViews(c, animate));
if (mIsOpen) {
mTaskbarContext.getDragLayer().removeView(this);
Optional.ofNullable(mOverlayContext).ifPresent(c -> closeAllOpenViews(c, animate));
}
}
@Override
@@ -38,6 +38,7 @@ import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -69,6 +70,7 @@ public class TaskbarOverlayDragLayer extends
return true;
}
};
private final List<TouchController> mTouchControllers = new ArrayList<>();
TaskbarOverlayDragLayer(Context context) {
super(context, null, 1);
@@ -93,10 +95,13 @@ public class TaskbarOverlayDragLayer extends
@Override
public void recreateControllers() {
mControllers = mOnClickListeners.isEmpty()
? new TouchController[]{mActivity.getDragController()}
: new TouchController[] {
mActivity.getDragController(), mClickListenerTouchController};
List<TouchController> controllers = new ArrayList<>();
controllers.add(mActivity.getDragController());
controllers.addAll(mTouchControllers);
if (!mOnClickListeners.isEmpty()) {
controllers.add(mClickListenerTouchController);
}
mControllers = controllers.toArray(new TouchController[0]);
}
@Override
@@ -113,6 +118,15 @@ public class TaskbarOverlayDragLayer extends
topView.onBackInvoked();
return true;
}
} else if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE && event.hasNoModifiers()) {
// Ignore escape if pressed in conjunction with any modifier keys. Close each
// floating view one at a time for each key press.
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
if (topView != null) {
topView.close(/* animate= */ true);
return true;
}
}
return super.dispatchKeyEvent(event);
}
@@ -183,6 +197,18 @@ public class TaskbarOverlayDragLayer extends
});
}
/** Adds a {@link TouchController} to this drag layer. */
public void addTouchController(@NonNull TouchController touchController) {
mTouchControllers.add(touchController);
recreateControllers();
}
/** Removes a {@link TouchController} from this drag layer. */
public void removeTouchController(@NonNull TouchController touchController) {
mTouchControllers.remove(touchController);
recreateControllers();
}
/**
* Taskbar automatically stashes when opening all apps, but we don't report the insets as
* changing to avoid moving the underlying app. But internally, the apps view should still
@@ -21,12 +21,20 @@ import android.app.Person;
import android.content.Context;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherUserInfo;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.ColorDrawable;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
import android.window.RemoteTransition;
import com.android.launcher3.Flags;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.UserIconInfo;
import com.android.quickstep.util.FadeOutRemoteTransition;
import java.util.List;
import java.util.Map;
/**
@@ -53,4 +61,56 @@ public class ApiWrapper {
options.setRemoteTransition(new RemoteTransition(new FadeOutRemoteTransition()));
return options;
}
/**
* Returns a map of all users on the device to their corresponding UI properties
*/
public static Map<UserHandle, UserIconInfo> queryAllUsers(Context context) {
UserManager um = context.getSystemService(UserManager.class);
Map<UserHandle, UserIconInfo> users = new ArrayMap<>();
List<UserHandle> usersActual = um.getUserProfiles();
if (usersActual != null) {
for (UserHandle user : usersActual) {
if (android.os.Flags.allowPrivateProfile() && Flags.enablePrivateSpace()) {
LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
LauncherUserInfo launcherUserInfo = launcherApps.getLauncherUserInfo(user);
// UserTypes not supported in Launcher are deemed to be the current
// Foreground User.
int userType = switch (launcherUserInfo.getUserType()) {
case UserManager.USER_TYPE_PROFILE_MANAGED -> UserIconInfo.TYPE_WORK;
case UserManager.USER_TYPE_PROFILE_CLONE -> UserIconInfo.TYPE_CLONED;
case UserManager.USER_TYPE_PROFILE_PRIVATE -> UserIconInfo.TYPE_PRIVATE;
default -> UserIconInfo.TYPE_MAIN;
};
long serial = launcherUserInfo.getUserSerialNumber();
users.put(user, new UserIconInfo(user, userType, serial));
} else {
long serial = um.getSerialNumberForUser(user);
// Simple check to check if the provided user is work profile
// TODO: Migrate to a better platform API
NoopDrawable d = new NoopDrawable();
boolean isWork = (d != context.getPackageManager().getUserBadgedIcon(d, user));
UserIconInfo info = new UserIconInfo(
user,
isWork ? UserIconInfo.TYPE_WORK : UserIconInfo.TYPE_MAIN,
serial);
users.put(user, info);
}
}
}
return users;
}
private static class NoopDrawable extends ColorDrawable {
@Override
public int getIntrinsicHeight() {
return 1;
}
@Override
public int getIntrinsicWidth() {
return 1;
}
}
}
@@ -107,8 +107,7 @@ public abstract class BaseRecentsViewStateController<T extends RecentsView>
setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
boolean exitingOverview = !FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
&& !toState.overviewUi;
boolean exitingOverview = !FeatureFlags.enableSplitContextually() && !toState.overviewUi;
if (mRecentsView.isSplitSelectionActive() && exitingOverview) {
setter.add(mRecentsView.getSplitSelectController().getSplitAnimationController()
.createPlaceholderDismissAnim(mLauncher));
@@ -44,13 +44,13 @@ import android.view.ViewGroup;
import androidx.core.graphics.ColorUtils;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.DelegatedCellDrawing;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.IconNormalizer;
@@ -418,7 +418,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
/**
* Draws Predicted Icon outline on cell layout
*/
public static class PredictedIconOutlineDrawing extends CellLayout.DelegatedCellDrawing {
public static class PredictedIconOutlineDrawing extends DelegatedCellDrawing {
private final PredictedAppIcon mIcon;
private final Paint mOutlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -27,8 +27,10 @@ import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.widget.RemoteViews;
import android.widget.Toast;
import android.window.SplashScreen;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
@@ -56,8 +58,9 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
return RemoteViews.startPendingIntent(hostView, pendingIntent,
remoteResponse.getLaunchOptions(view));
}
if (mLauncher.getSplitToWorkspaceController().handleSecondWidgetSelectionForSplit(view,
pendingIntent)) {
if (mLauncher.isSplitSelectionEnabled()) {
Toast.makeText(hostView.getContext(), R.string.split_widgets_not_supported,
Toast.LENGTH_SHORT).show();
return true;
}
Pair<Intent, ActivityOptions> options = remoteResponse.getLaunchOptions(view);
@@ -21,6 +21,8 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEAS
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
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;
@@ -33,7 +35,6 @@ 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.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE;
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;
@@ -50,9 +51,10 @@ import static com.android.launcher3.testing.shared.TestProtocol.QUICK_SWITCH_STA
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -61,7 +63,6 @@ import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Rect;
@@ -93,6 +94,7 @@ import androidx.annotation.RequiresApi;
import com.android.app.viewcapture.SettingsAwareViewCapture;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.HomeTransitionController;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherState;
@@ -104,8 +106,10 @@ import com.android.launcher3.Workspace;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.appprediction.PredictionRowView;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.desktop.DesktopRecentsTransitionController;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
@@ -113,7 +117,6 @@ 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.statehandlers.DepthController;
@@ -163,7 +166,6 @@ import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.SplitToWorkspaceController;
import com.android.quickstep.util.SplitWithKeyboardShortcutController;
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.FloatingTaskView;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
@@ -185,6 +187,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -217,29 +220,40 @@ public class QuickstepLauncher extends Launcher {
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
* recover. In all other cases this will remain null.
*/
private PendingSplitSelectInfo mPendingSplitSelectInfo = null;
@Nullable
private DesktopRecentsTransitionController mDesktopRecentsTransitionController;
private SafeCloseable mViewCapture;
private boolean mEnableWidgetDepth;
private HomeTransitionController mHomeTransitionController;
@Override
protected void setupViews() {
super.setupViews();
mActionsView = findViewById(R.id.overview_actions_view);
RecentsView overviewPanel = getOverviewPanel();
SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this);
mSplitSelectStateController =
new SplitSelectStateController(this, mHandler, getStateManager(),
getDepthController(), getStatsLogManager(),
SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this));
overviewPanel.init(mActionsView, mSplitSelectStateController);
systemUiProxy, RecentsModel.INSTANCE.get(this),
() -> onStateBack());
if (isDesktopModeSupported()) {
mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
getStateManager(), systemUiProxy, getIApplicationThread(),
getDepthController());
}
overviewPanel.init(mActionsView, mSplitSelectStateController,
mDesktopRecentsTransitionController);
mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(this,
mSplitSelectStateController);
mSplitToWorkspaceController = new SplitToWorkspaceController(this,
@@ -251,10 +265,15 @@ public class QuickstepLauncher extends Launcher {
mAppTransitionManager.registerRemoteAnimations();
mAppTransitionManager.registerRemoteTransitions();
if (FeatureFlags.enableHomeTransitionListener()) {
mHomeTransitionController = new HomeTransitionController(this);
mHomeTransitionController.registerHomeTransitionListener();
}
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
mDepthController = new DepthController(this);
mDesktopVisibilityController = new DesktopVisibilityController(this);
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
if (isDesktopModeSupported()) {
mDesktopVisibilityController.registerSystemUiListener();
mSplitSelectStateController.initSplitFromDesktopController(this);
}
@@ -263,6 +282,7 @@ public class QuickstepLauncher extends Launcher {
mEnableWidgetDepth = SystemProperties.getBoolean("ro.launcher.depth.widget", true);
getWorkspace().addOverlayCallback(progress ->
onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX));
addBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler());
}
@Override
@@ -331,11 +351,6 @@ public class QuickstepLauncher extends Launcher {
return new QuickstepTransitionManager(this);
}
@Override
protected QuickstepOnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
return new QuickstepOnboardingPrefs(this, sharedPrefs);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -362,8 +377,8 @@ public class QuickstepLauncher extends Launcher {
}
if ((changeBits & ACTIVITY_STATE_RESUMED) != 0) {
if (mTaskbarUIController != null) {
mTaskbarUIController.onLauncherResumedOrPaused(hasBeenResumed());
if (!FeatureFlags.enableHomeTransitionListener() && mTaskbarUIController != null) {
mTaskbarUIController.onLauncherVisibilityChanged(hasBeenResumed());
}
}
@@ -464,7 +479,11 @@ public class QuickstepLauncher extends Launcher {
@Override
public void onDestroy() {
mAppTransitionManager.onActivityDestroyed();
if (mAppTransitionManager != null) {
mAppTransitionManager.onActivityDestroyed();
}
mAppTransitionManager = null;
if (mUnfoldTransitionProgressProvider != null) {
SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(null);
mUnfoldTransitionProgressProvider.destroy();
@@ -476,6 +495,10 @@ public class QuickstepLauncher extends Launcher {
mLauncherUnfoldAnimationController.onDestroy();
}
if (mHomeTransitionController != null) {
mHomeTransitionController.unregisterHomeTransitionListener();
}
if (mDesktopVisibilityController != null) {
mDesktopVisibilityController.unregisterSystemUiListener();
}
@@ -484,14 +507,11 @@ public class QuickstepLauncher extends Launcher {
mSplitSelectStateController.onDestroy();
}
if (mAsyncClockEventDelegate != null) {
mAsyncClockEventDelegate.onDestroy();
}
super.onDestroy();
mHotseatPredictionController.destroy();
mSplitWithKeyboardShortcutController.onDestroy();
if (mViewCapture != null) mViewCapture.close();
removeBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler());
}
@Override
@@ -610,6 +630,7 @@ public class QuickstepLauncher extends Launcher {
mViewCapture = SettingsAwareViewCapture.getInstance(this).startCapture(getWindow());
}
getWindow().addPrivateFlags(PRIVATE_FLAG_OPTIMIZE_MEASURE);
QuickstepOnboardingPrefs.setup(this);
View.setTraceLayoutSteps(TRACE_LAYOUTS);
View.setTracedRequestLayoutClassClass(TRACE_RELAYOUT_CLASS);
}
@@ -621,13 +642,14 @@ public class QuickstepLauncher extends Launcher {
// using that.
mSplitSelectStateController.findLastActiveTasksAndRunCallback(
Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()),
false /* findExactPairMatch */,
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()) {
if (FeatureFlags.enableSplitContextually()) {
startSplitToHome(splitSelectSource);
} else {
recentsView.initiateSplitSelect(splitSelectSource);
@@ -661,6 +683,9 @@ public class QuickstepLauncher extends Launcher {
floatingTaskView.setAlpha(1);
floatingTaskView.addStagingAnimation(anim, startingTaskRect, tempRect,
false /* fadeWithThumbnail */, true /* isStagedTask */);
floatingTaskView.setOnClickListener(view ->
mSplitSelectStateController.getSplitAnimationController().
playAnimPlaceholderToFullscreen(this, view, Optional.empty()));
mSplitSelectStateController.setFirstFloatingTaskView(floatingTaskView);
anim.addListener(new AnimatorListenerAdapter() {
@Override
@@ -676,6 +701,17 @@ public class QuickstepLauncher extends Launcher {
anim.buildAnim().start();
}
@Override
public boolean isSplitSelectionEnabled() {
return mSplitSelectStateController.isSplitSelectActive();
}
@Override
public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) {
if (mTaskbarUIController != null) {
mTaskbarUIController.onStateTransitionCompletedAfterSwipeToHome(finalState);
}
}
@Override
protected void onResume() {
@@ -693,6 +729,15 @@ public class QuickstepLauncher extends Launcher {
}
super.onPause();
if (FeatureFlags.enableSplitContextually()) {
// If Launcher pauses before both split apps are selected, exit split screen.
if (!mSplitSelectStateController.isBothSplitAppsConfirmed() &&
!mSplitSelectStateController.isLaunchingFirstAppFullscreen()) {
mSplitSelectStateController.getSplitAnimationController()
.playPlaceholderDismissAnim(this);
}
}
}
@Override
@@ -738,15 +783,6 @@ public class QuickstepLauncher extends Launcher {
() -> ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(this));
}
@Override
protected void onScreenOnChanged(boolean isOn) {
super.onScreenOnChanged(isOn);
if (!isOn) {
RecentsView recentsView = getOverviewPanel();
recentsView.finishRecentsAnimation(true /* toRecents */, null);
}
}
@Override
public void onAllAppsTransition(float progress) {
super.onAllAppsTransition(progress);
@@ -866,7 +902,7 @@ public class QuickstepLauncher extends Launcher {
@Override
public void setResumed() {
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
if (isDesktopModeSupported()) {
DesktopVisibilityController controller = mDesktopVisibilityController;
if (controller != null && controller.areFreeformTasksVisible()
&& !controller.isRecentsGestureInProgress()) {
@@ -971,6 +1007,13 @@ public class QuickstepLauncher extends Launcher {
.playPlaceholderDismissAnim(this);
}
@Override
public void dismissSplitSelection() {
super.dismissSplitSelection();
mSplitSelectStateController.getSplitAnimationController()
.playPlaceholderDismissAnim(this);
}
public <T extends OverviewActionsView> T getActionsView() {
return (T) mActionsView;
}
@@ -1003,7 +1046,7 @@ public class QuickstepLauncher extends Launcher {
@Override
public boolean supportsAdaptiveIconAnimation(View clickedView) {
return mAppTransitionManager.hasControlRemoteAppTransitionPermission();
return true;
}
@Override
@@ -1038,10 +1081,7 @@ public class QuickstepLauncher extends Launcher {
@Override
public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
ActivityOptionsWrapper activityOptions =
mAppTransitionManager.hasControlRemoteAppTransitionPermission()
? mAppTransitionManager.getActivityLaunchOptions(v)
: super.getActivityLaunchOptions(v, item);
ActivityOptionsWrapper activityOptions = mAppTransitionManager.getActivityLaunchOptions(v);
if (mLastTouchUpTime > 0) {
activityOptions.options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER,
mLastTouchUpTime);
@@ -1155,11 +1195,6 @@ public class QuickstepLauncher extends Launcher {
}
}
@Override
public void tryClearAccessibilityFocus(View view) {
view.clearAccessibilityFocus();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@@ -1235,36 +1270,29 @@ public class QuickstepLauncher extends Launcher {
/**
* Launches the given {@link GroupTask} in splitscreen.
*
* If the second split task is missing, launches the first task normally.
*/
public void launchSplitTasks(@NonNull View taskView, @NonNull GroupTask groupTask) {
if (groupTask.task2 == null) {
UI_HELPER_EXECUTOR.execute(() ->
ActivityManagerWrapper.getInstance().startActivityFromRecents(
groupTask.task1.key,
getActivityLaunchOptions(taskView, null).options));
return;
}
public void launchSplitTasks(@NonNull GroupTask groupTask) {
// Top/left and bottom/right tasks respectively.
Task task1 = groupTask.task1;
// task2 should never be null when calling this method. Allow a crash to catch invalid calls
Task task2 = groupTask.task2;
mSplitSelectStateController.launchExistingSplitPair(
null /* launchingTaskView */,
groupTask.task1.key.id,
groupTask.task2.key.id,
task1.key.id,
task2.key.id,
SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
/* callback= */ success -> mSplitSelectStateController.resetState(),
/* freezeTaskList= */ true,
/* freezeTaskList= */ false,
groupTask.mSplitBounds == null
? DEFAULT_SPLIT_RATIO
: groupTask.mSplitBounds.appsStackedVertically
? groupTask.mSplitBounds.topTaskPercent
: groupTask.mSplitBounds.leftTaskPercent);
? SNAP_TO_50_50
: groupTask.mSplitBounds.snapPosition);
}
/**
* Launches two apps as an app pair.
*/
public void launchAppPair(WorkspaceItemInfo app1, WorkspaceItemInfo app2) {
mSplitSelectStateController.getAppPairsController().launchAppPair(app1, app2);
public void launchAppPair(AppPairIcon appPairIcon) {
mSplitSelectStateController.getAppPairsController().launchAppPair(appPairIcon);
}
public boolean canStartHomeSafely() {
@@ -1272,6 +1300,16 @@ public class QuickstepLauncher extends Launcher {
return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely();
}
@Override
public boolean isBubbleBarEnabled() {
return (mTaskbarUIController != null && mTaskbarUIController.isBubbleBarEnabled());
}
@Override
public boolean hasBubbles() {
return (mTaskbarUIController != null && mTaskbarUIController.hasBubbles());
}
private static final class LauncherTaskViewController extends
TaskViewTouchController<Launcher> {
@@ -1321,18 +1359,12 @@ public class QuickstepLauncher extends Launcher {
switch (name) {
case "TextClock", "android.widget.TextClock" -> {
TextClock tc = new TextClock(context, attrs);
if (mAsyncClockEventDelegate == null) {
mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
}
tc.setClockEventDelegate(mAsyncClockEventDelegate);
tc.setClockEventDelegate(AsyncClockEventDelegate.INSTANCE.get(this));
return tc;
}
case "AnalogClock", "android.widget.AnalogClock" -> {
AnalogClock ac = new AnalogClock(context, attrs);
if (mAsyncClockEventDelegate == null) {
mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
}
ac.setClockEventDelegate(mAsyncClockEventDelegate);
ac.setClockEventDelegate(AsyncClockEventDelegate.INSTANCE.get(this));
return ac;
}
}
@@ -37,6 +37,7 @@ import com.android.launcher3.util.IntSet;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.LauncherWidgetHolder;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.util.ArrayList;
import java.util.Collections;
@@ -237,6 +238,14 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
@Override
public LauncherAppWidgetHostView createView(@NonNull Context context, int appWidgetId,
@NonNull LauncherAppWidgetProviderInfo appWidget) {
if (appWidget.isCustomWidget()) {
LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context);
lahv.setAppWidget(appWidgetId, appWidget);
CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
return lahv;
}
LauncherAppWidgetHostView widgetView = getPendingView(appWidgetId);
if (widgetView != null) {
removePendingView(appWidgetId);
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.uioverrides.flags;
import static com.android.launcher3.uioverrides.flags.FlagsFactory.TEAMFOOD_FLAG;
import androidx.annotation.NonNull;
import com.android.launcher3.config.FeatureFlags.BooleanFlag;
@@ -35,6 +37,21 @@ class DebugFlag extends BooleanFlag {
this.description = description;
}
/**
* Returns {@code true} if this flag's value has been modified from its default.
* <p>
* This helps to identify which flags have been toggled in log dumps and bug reports to
* further help triaging and debugging.
*/
boolean currentValueModified() {
switch (defaultValue) {
case ENABLED: return !get();
case TEAMFOOD: return TEAMFOOD_FLAG.get() != get();
case DISABLED: return get();
default: return true;
}
}
@Override
public String toString() {
return key + ": defaultValue=" + defaultValue + ", mCurrentValue=" + get();
@@ -15,20 +15,30 @@
*/
package com.android.launcher3.uioverrides.flags;
import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.content.pm.PackageManager.GET_RESOLVED_FILTER;
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.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY;
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT;
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS;
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT;
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT;
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE;
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_TIMEOUT_MS;
import static com.android.launcher3.LauncherPrefs.PRIVATE_SPACE_APPS;
import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_HIGHLIGHT_KEY;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_COUNT;
import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_SEEN;
import static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_DISCOVERY_TIP_COUNT;
import static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN;
import static com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP;
import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -36,23 +46,17 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.ArrayMap;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceDataStore;
@@ -63,17 +67,16 @@ import androidx.preference.PreferenceViewHolder;
import androidx.preference.SeekBarPreference;
import androidx.preference.SwitchPreference;
import com.android.launcher3.ConstantItem;
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@@ -81,39 +84,41 @@ import java.util.stream.Collectors;
* Dev-build only UI allowing developers to toggle flag settings and plugins.
* See {@link FeatureFlags}.
*/
@TargetApi(Build.VERSION_CODES.O)
public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
public class DeveloperOptionsUI {
private static final String ACTION_PLUGIN_SETTINGS = "com.android.systemui.action.PLUGIN_SETTINGS";
private static final String ACTION_PLUGIN_SETTINGS =
"com.android.systemui.action.PLUGIN_SETTINGS";
private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
private final SimpleBroadcastReceiver mPluginReceiver =
new SimpleBroadcastReceiver(i -> loadPluginPrefs());
private PreferenceScreen mPreferenceScreen;
private final PreferenceFragmentCompat mFragment;
private final PreferenceScreen mPreferenceScreen;
private PreferenceCategory mPluginsCategory;
private FlagTogglerPrefUi mFlagTogglerPrefUi;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
mPluginReceiver.registerPkgActions(getContext(), null,
ACTION_PACKAGE_ADDED, ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REMOVED);
mPluginReceiver.register(getContext(), Intent.ACTION_USER_UNLOCKED);
public DeveloperOptionsUI(PreferenceFragmentCompat fragment, PreferenceCategory flags) {
mFragment = fragment;
mPreferenceScreen = fragment.getPreferenceScreen();
mPreferenceScreen = getPreferenceManager().createPreferenceScreen(getContext());
setPreferenceScreen(mPreferenceScreen);
// Add search bar
View listView = mFragment.getListView();
ViewGroup parent = (ViewGroup) listView.getParent();
View topBar = LayoutInflater.from(parent.getContext())
.inflate(R.layout.developer_options_top_bar, parent, false);
parent.addView(topBar, parent.indexOfChild(listView));
initSearch(topBar.findViewById(R.id.filter_box));
new FlagTogglerPrefUi(mFragment.requireActivity(), topBar.findViewById(R.id.flag_apply_btn))
.applyTo(flags);
initFlags();
loadPluginPrefs();
maybeAddSandboxCategory();
addOnboardingPrefsCatergory();
if (FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()) {
addAllAppsFromOverviewCatergory();
}
if (getActivity() != null) {
getActivity().setTitle("Developer Options");
addCustomLpnhCategory();
if (Flags.enablePrivateSpace()) {
addCustomPrivateAppsCategory();
}
}
@@ -137,21 +142,13 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
pg.setVisible(hidden != count);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
EditText filterBox = view.findViewById(R.id.filter_box);
filterBox.setVisibility(VISIBLE);
private void initSearch(EditText filterBox) {
filterBox.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
@Override
public void afterTextChanged(Editable editable) {
@@ -160,85 +157,33 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
}
});
if (getArguments() != null) {
String filter = getArguments().getString(EXTRA_FRAGMENT_ARG_KEY);
if (mFragment.getArguments() != null) {
String filter = mFragment.getArguments().getString(EXTRA_FRAGMENT_HIGHLIGHT_KEY);
// Normally EXTRA_FRAGMENT_ARG_KEY is used to highlight the preference with the given
// key. This is a slight variation where we instead filter by the human-readable titles.
if (filter != null) {
filterBox.setText(filter);
}
}
View listView = getListView();
final int bottomPadding = listView.getPaddingBottom();
listView.setOnApplyWindowInsetsListener((v, insets) -> {
v.setPadding(
v.getPaddingLeft(),
v.getPaddingTop(),
v.getPaddingRight(),
bottomPadding + insets.getSystemWindowInsetBottom());
return insets.consumeSystemWindowInsets();
});
}
@Override
public void onDestroy() {
super.onDestroy();
mPluginReceiver.unregisterReceiverSafely(getContext());
}
private PreferenceCategory newCategory(String title) {
return newCategory(title, null);
}
private PreferenceCategory newCategory(String title, @Nullable String summary) {
PreferenceCategory category = new PreferenceCategory(getContext());
category.setOrder(Preference.DEFAULT_ORDER);
category.setTitle(title);
if (!TextUtils.isEmpty(summary)) {
category.setSummary(summary);
}
mPreferenceScreen.addPreference(category);
return category;
}
private void initFlags() {
if (!FeatureFlags.showFlagTogglerUi(getContext())) {
return;
}
mFlagTogglerPrefUi = new FlagTogglerPrefUi(this);
mFlagTogglerPrefUi.applyTo(newCategory("Feature flags", "Long press to reset"));
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (mFlagTogglerPrefUi != null) {
mFlagTogglerPrefUi.onCreateOptionsMenu(menu);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (mFlagTogglerPrefUi != null) {
mFlagTogglerPrefUi.onOptionsItemSelected(item);
}
return super.onOptionsItemSelected(item);
}
@Override
public void onStop() {
if (mFlagTogglerPrefUi != null) {
mFlagTogglerPrefUi.onStop();
}
super.onStop();
private Context getContext() {
return mFragment.requireContext();
}
private void loadPluginPrefs() {
if (mPluginsCategory != null) {
mPreferenceScreen.removePreference(mPluginsCategory);
}
if (!PluginManagerWrapper.hasPlugins(getActivity())) {
if (!PluginManagerWrapper.hasPlugins(getContext())) {
mPluginsCategory = null;
return;
}
@@ -311,118 +256,159 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
launchTutorialStepMenuPreference.setKey("launchTutorialStepMenu");
launchTutorialStepMenuPreference.setTitle("Launch Gesture Tutorial Steps menu");
launchTutorialStepMenuPreference.setSummary("Select a gesture tutorial step.");
launchTutorialStepMenuPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra("use_tutorial_menu", true));
return true;
});
launchTutorialStepMenuPreference.setIntent(
new Intent(launchSandboxIntent).putExtra("use_tutorial_menu", true));
sandboxCategory.addPreference(launchTutorialStepMenuPreference);
Preference launchOnboardingTutorialPreference = new Preference(context);
launchOnboardingTutorialPreference.setKey("launchOnboardingTutorial");
launchOnboardingTutorialPreference.setTitle("Launch Onboarding Tutorial");
launchOnboardingTutorialPreference.setSummary("Learn the basic navigation gestures.");
launchOnboardingTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent
.putExtra("use_tutorial_menu", false)
.putExtra("tutorial_steps",
new String[] {
"HOME_NAVIGATION",
"BACK_NAVIGATION",
"OVERVIEW_NAVIGATION"}));
return true;
});
launchTutorialStepMenuPreference.setIntent(new Intent(launchSandboxIntent)
.putExtra("use_tutorial_menu", false)
.putExtra("tutorial_steps",
new String[] {
"HOME_NAVIGATION",
"BACK_NAVIGATION",
"OVERVIEW_NAVIGATION"}));
sandboxCategory.addPreference(launchOnboardingTutorialPreference);
Preference launchBackTutorialPreference = new Preference(context);
launchBackTutorialPreference.setKey("launchBackTutorial");
launchBackTutorialPreference.setTitle("Launch Back Tutorial");
launchBackTutorialPreference.setSummary("Learn how to use the Back gesture");
launchBackTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent
launchBackTutorialPreference.setIntent(new Intent(launchSandboxIntent)
.putExtra("use_tutorial_menu", false)
.putExtra("tutorial_steps", new String[] {"BACK_NAVIGATION"}));
return true;
});
sandboxCategory.addPreference(launchBackTutorialPreference);
Preference launchHomeTutorialPreference = new Preference(context);
launchHomeTutorialPreference.setKey("launchHomeTutorial");
launchHomeTutorialPreference.setTitle("Launch Home Tutorial");
launchHomeTutorialPreference.setSummary("Learn how to use the Home gesture");
launchHomeTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent
launchHomeTutorialPreference.setIntent(new Intent(launchSandboxIntent)
.putExtra("use_tutorial_menu", false)
.putExtra("tutorial_steps", new String[] {"HOME_NAVIGATION"}));
return true;
});
sandboxCategory.addPreference(launchHomeTutorialPreference);
Preference launchOverviewTutorialPreference = new Preference(context);
launchOverviewTutorialPreference.setKey("launchOverviewTutorial");
launchOverviewTutorialPreference.setTitle("Launch Overview Tutorial");
launchOverviewTutorialPreference.setSummary("Learn how to use the Overview gesture");
launchOverviewTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent
launchOverviewTutorialPreference.setIntent(new Intent(launchSandboxIntent)
.putExtra("use_tutorial_menu", false)
.putExtra("tutorial_steps", new String[] {"OVERVIEW_NAVIGATION"}));
return true;
});
sandboxCategory.addPreference(launchOverviewTutorialPreference);
Preference launchSecondaryDisplayPreference = new Preference(context);
launchSecondaryDisplayPreference.setKey("launchSecondaryDisplay");
launchSecondaryDisplayPreference.setTitle("Launch Secondary Display");
launchSecondaryDisplayPreference.setSummary("Launch secondary display activity");
launchSecondaryDisplayPreference.setOnPreferenceClickListener(preference -> {
startActivity(new Intent(context, SecondaryDisplayLauncher.class));
return true;
});
sandboxCategory.addPreference(launchSecondaryDisplayPreference);
launchSecondaryDisplayPreference.setIntent(
new Intent(context, SecondaryDisplayLauncher.class));
}
private void addOnboardingPrefsCatergory() {
PreferenceCategory onboardingCategory = newCategory("Onboarding Flows");
onboardingCategory.setSummary("Reset these if you want to see the education again.");
for (Map.Entry<String, String[]> titleAndKeys : OnboardingPrefs.ALL_PREF_KEYS.entrySet()) {
String title = titleAndKeys.getKey();
String[] keys = titleAndKeys.getValue();
Preference onboardingPref = new Preference(getContext());
onboardingPref.setTitle(title);
onboardingPref.setSummary("Tap to reset");
onboardingPref.setOnPreferenceClickListener(preference -> {
SharedPreferences.Editor sharedPrefsEdit = LauncherPrefs.getPrefs(getContext())
.edit();
for (String key : keys) {
sharedPrefsEdit.remove(key);
}
sharedPrefsEdit.apply();
Toast.makeText(getContext(), "Reset " + title, Toast.LENGTH_SHORT).show();
return true;
});
onboardingCategory.addPreference(onboardingPref);
}
onboardingCategory.addPreference(createOnboardPref("All Apps Bounce",
HOME_BOUNCE_SEEN.getSharedPrefKey(), HOME_BOUNCE_COUNT.getSharedPrefKey()));
onboardingCategory.addPreference(createOnboardPref("Hybrid Hotseat Education",
HOTSEAT_DISCOVERY_TIP_COUNT.getSharedPrefKey(),
HOTSEAT_LONGPRESS_TIP_SEEN.getSharedPrefKey()));
onboardingCategory.addPreference(createOnboardPref("Taskbar Education",
TASKBAR_EDU_TOOLTIP_STEP.getSharedPrefKey()));
onboardingCategory.addPreference(createOnboardPref("All Apps Visited Count",
ALL_APPS_VISITED_COUNT.getSharedPrefKey()));
}
private Preference createOnboardPref(String title, String... keys) {
Preference onboardingPref = new Preference(getContext());
onboardingPref.setTitle(title);
onboardingPref.setSummary("Tap to reset");
onboardingPref.setOnPreferenceClickListener(preference -> {
SharedPreferences.Editor sharedPrefsEdit = LauncherPrefs.getPrefs(getContext())
.edit();
for (String key : keys) {
sharedPrefsEdit.remove(key);
}
sharedPrefsEdit.apply();
Toast.makeText(getContext(), "Reset " + title, Toast.LENGTH_SHORT).show();
return true;
});
return onboardingPref;
}
private void addAllAppsFromOverviewCatergory() {
PreferenceCategory category = newCategory("All Apps from Overview Config");
category.addPreference(createSeekBarPreference("Threshold to open All Apps from Overview",
105, 500, 100, ALL_APPS_OVERVIEW_THRESHOLD));
}
SeekBarPreference thresholdPref = new SeekBarPreference(getContext());
thresholdPref.setTitle("Threshold to open All Apps from Overview");
thresholdPref.setSingleLineTitle(false);
private void addCustomLpnhCategory() {
PreferenceCategory category = newCategory("Long Press Nav Handle Config");
if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
category.addPreference(createSeekBarPreference("Slop multiplier (applied to edge slop, "
+ "which is generally already 50% higher than touch slop)",
25, 200, 100, LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE));
category.addPreference(createSeekBarPreference("Trigger milliseconds",
100, 500, 1, LONG_PRESS_NAV_HANDLE_TIMEOUT_MS));
}
if (FeatureFlags.ENABLE_SEARCH_HAPTIC_HINT.get()) {
category.addPreference(createSeekBarPreference("Haptic hint start scale",
0, 100, 100, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT));
category.addPreference(createSeekBarPreference("Haptic hint end scale",
0, 100, 100, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT));
category.addPreference(createSeekBarPreference("Haptic hint scale exponent",
1, 5, 1, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT));
category.addPreference(createSeekBarPreference("Haptic hint iterations (12 ms each)",
0, 200, 1, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS));
category.addPreference(createSeekBarPreference("Haptic hint delay (ms)",
0, 400, 1, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY));
}
}
// These values are 100x swipe up shift value (100 = where overview sits).
thresholdPref.setMax(500);
thresholdPref.setMin(105);
thresholdPref.setUpdatesContinuously(true);
thresholdPref.setIconSpaceReserved(false);
private void addCustomPrivateAppsCategory() {
PreferenceCategory category = newCategory("Apps in Private Space Config");
category.addPreference(createSeekBarPreference(
"Number of Apps to put in private region", 0, 100, 1, PRIVATE_SPACE_APPS));
}
/**
* Create a preference with text and a seek bar. Should be added to a PreferenceCategory.
*
* @param title text to show for this seek bar
* @param min min value for the seek bar
* @param max max value for the seek bar
* @param scale how much to divide the value to convert int to float
* @param launcherPref used to store the current value
*/
private SeekBarPreference createSeekBarPreference(String title, int min, int max, int scale,
ConstantItem<Integer> launcherPref) {
SeekBarPreference seekBarPref = new SeekBarPreference(getContext());
seekBarPref.setTitle(title);
seekBarPref.setSingleLineTitle(false);
seekBarPref.setMax(max);
seekBarPref.setMin(min);
seekBarPref.setUpdatesContinuously(true);
seekBarPref.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));
seekBarPref.setPersistent(false);
seekBarPref.setOnPreferenceChangeListener((preference, newValue) -> {
LauncherPrefs.get(getContext()).put(launcherPref, newValue);
preference.setSummary(String.valueOf(scale == 1 ? newValue
: (int) newValue / (float) scale));
return true;
});
int value = LauncherPrefs.get(getContext()).get(ALL_APPS_OVERVIEW_THRESHOLD);
thresholdPref.setValue(value);
int value = LauncherPrefs.get(getContext()).get(launcherPref);
seekBarPref.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);
seekBarPref.setSummary(String.valueOf(scale == 1 ? value
: value / (float) scale));
return seekBarPref;
}
private String toName(String action) {
@@ -28,6 +28,11 @@ class DeviceFlag extends DebugFlag {
mDefaultValueInCode = defaultValueInCode;
}
@Override
boolean currentValueModified() {
return super.currentValueModified() || mDefaultValueInCode != get();
}
@Override
public String toString() {
return super.toString() + ", mDefaultValueInCode=" + mDefaultValueInCode;
@@ -19,24 +19,23 @@ package com.android.launcher3.uioverrides.flags;
import static com.android.launcher3.config.FeatureFlags.FlagState.TEAMFOOD;
import static com.android.launcher3.uioverrides.flags.FlagsFactory.TEAMFOOD_FLAG;
import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.os.Process;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.preference.PreferenceDataStore;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreference;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
import java.util.List;
import java.util.Set;
@@ -44,12 +43,13 @@ import java.util.Set;
/**
* Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}.
*/
public final class FlagTogglerPrefUi {
public final class FlagTogglerPrefUi implements ActivityLifecycleCallbacksAdapter {
private static final String TAG = "FlagTogglerPrefFrag";
private final PreferenceFragmentCompat mFragment;
private final View mFlagsApplyButton;
private final Context mContext;
private final PreferenceDataStore mDataStore = new PreferenceDataStore() {
@Override
@@ -64,9 +64,17 @@ public final class FlagTogglerPrefUi {
}
};
public FlagTogglerPrefUi(PreferenceFragmentCompat fragment) {
mFragment = fragment;
mContext = fragment.getActivity();
public FlagTogglerPrefUi(Activity activity, View flagsApplyButton) {
mFlagsApplyButton = flagsApplyButton;
mContext = mFlagsApplyButton.getContext();
activity.registerActivityLifecycleCallbacks(this);
mFlagsApplyButton.setOnClickListener(v -> {
FlagsFactory.getSharedPreferences().edit().commit();
Log.e(TAG,
"Killing launcher process " + Process.myPid() + " to apply new flag values");
System.exit(0);
});
}
public void applyTo(PreferenceGroup parent) {
@@ -137,27 +145,11 @@ public final class FlagTogglerPrefUi {
}
private void updateMenu() {
mFragment.setHasOptionsMenu(anyChanged());
mFragment.getActivity().invalidateOptionsMenu();
mFlagsApplyButton.setVisibility(anyChanged() ? View.VISIBLE : View.INVISIBLE);
}
public void onCreateOptionsMenu(Menu menu) {
if (anyChanged()) {
menu.add(0, R.id.menu_apply_flags, 0, "Apply")
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
}
public void onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_apply_flags) {
FlagsFactory.getSharedPreferences().edit().commit();
Log.e(TAG,
"Killing launcher process " + Process.myPid() + " to apply new flag values");
System.exit(0);
}
}
public void onStop() {
@Override
public void onActivityStopped(Activity activity) {
if (anyChanged()) {
Toast.makeText(mContext, "Flag won't be applied until you restart launcher",
Toast.LENGTH_LONG).show();
@@ -57,6 +57,7 @@ public class FlagsFactory {
public static final String NAMESPACE_LAUNCHER = "launcher";
private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
private static final List<IntFlag> sIntFlags = new ArrayList<>();
private static SharedPreferences sSharedPreferences;
static final BooleanFlag TEAMFOOD_FLAG = getReleaseFlag(
@@ -132,7 +133,14 @@ public class FlagsFactory {
public static IntFlag getIntFlag(
int bugId, String key, int defaultValueInCode, String description) {
INSTANCE.mKeySet.add(key);
return new IntFlag(DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, defaultValueInCode));
int defaultValue = DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, defaultValueInCode);
if (IS_DEBUG_DEVICE) {
IntDeviceFlag flag = new IntDeviceFlag(key, defaultValue, defaultValueInCode);
sIntFlags.add(flag);
return flag;
} else {
return new IntFlag(defaultValue);
}
}
static List<DebugFlag> getDebugFlags() {
@@ -163,18 +171,25 @@ public class FlagsFactory {
return;
}
pw.println("DeviceFlags:");
pw.println(" BooleanFlags:");
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
if (flag instanceof DeviceFlag) {
pw.println(" " + flag);
pw.println((flag.currentValueModified() ? " ->" : " ") + flag);
}
}
}
pw.println("DebugFlags:");
pw.println(" IntFlags:");
synchronized (sIntFlags) {
for (IntFlag flag : sIntFlags) {
pw.println(" " + flag);
}
}
pw.println(" DebugFlags:");
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
if (!(flag instanceof DeviceFlag)) {
pw.println(" " + flag);
pw.println((flag.currentValueModified() ? " ->" : " ") + flag);
}
}
}
@@ -0,0 +1,34 @@
/*
* 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.uioverrides.flags;
import com.android.launcher3.config.FeatureFlags.IntFlag;
public class IntDeviceFlag extends IntFlag {
public final String key;
private final int mDefaultValueInCode;
public IntDeviceFlag(String key, int currentValue, int defaultValueInCode) {
super(currentValue);
this.key = key;
mDefaultValueInCode = defaultValueInCode;
}
@Override
public String toString() {
return key + ": mCurrentValue=" + get() + ", defaultValueInCode=" + mDefaultValueInCode;
}
}
@@ -121,13 +121,13 @@ public class AllAppsState extends LauncherState {
@Override
public int getFloatingSearchBarRestingMarginStart(Launcher launcher) {
DeviceProfile dp = launcher.getDeviceProfile();
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin();
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin(launcher);
}
@Override
public int getFloatingSearchBarRestingMarginEnd(Launcher launcher) {
DeviceProfile dp = launcher.getDeviceProfile();
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin();
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin(launcher);
}
@Override
@@ -17,6 +17,7 @@ package com.android.launcher3.uioverrides.states;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import android.content.Context;
import android.graphics.Color;
@@ -26,7 +27,6 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
/**
@@ -90,7 +90,7 @@ public class BackgroundAppState extends OverviewState {
@Override
protected float getDepthUnchecked(Context context) {
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
if (isDesktopModeSupported()) {
if (Launcher.getLauncher(context).areFreeformTasksVisible()) {
// Don't blur the background while freeform tasks are visible
return 0;
@@ -21,9 +21,9 @@ import android.content.Context;
import android.graphics.Rect;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.config.FeatureFlags;
import com.android.quickstep.views.RecentsView;
/**
@@ -72,7 +72,7 @@ public class OverviewModalTaskState extends OverviewState {
@Override
public boolean isTaskbarStashed(Launcher launcher) {
if (FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) {
if (Flags.enableGridOnlyOverview()) {
return true;
}
return super.isTaskbarStashed(launcher);
@@ -16,6 +16,7 @@
package com.android.launcher3.uioverrides.states;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import android.graphics.Color;
@@ -23,7 +24,6 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.util.Themes;
import com.android.quickstep.views.DesktopTaskView;
/**
* State to indicate we are about to launch a recent task. Note that this state is only used when
@@ -46,7 +46,7 @@ public class QuickSwitchState extends BackgroundAppState {
@Override
public int getWorkspaceScrimColor(Launcher launcher) {
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
if (isDesktopModeSupported()) {
if (launcher.areFreeformTasksVisible()) {
// No scrim while freeform tasks are visible
return Color.TRANSPARENT;
@@ -45,6 +45,7 @@ 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.statemanager.StateManager;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.TouchController;
@@ -194,8 +195,21 @@ public class NavBarToHomeTouchController implements TouchController,
recentsView.switchToScreenshot(null,
() -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
if (mStartState.overviewUi) {
new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState),
FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
Runnable onReachedHome = () -> {
StateManager.StateListener<LauncherState> listener =
new StateManager.StateListener<>() {
@Override
public void onStateTransitionComplete(LauncherState finalState) {
mLauncher.onStateTransitionCompletedAfterSwipeToHome(
finalState);
mLauncher.getStateManager().removeStateListener(this);
}
};
mLauncher.getStateManager().addStateListener(listener);
onSwipeInteractionCompleted(mEndState);
};
new OverviewToHomeAnim(mLauncher, onReachedHome,
FeatureFlags.enableSplitContextually()
? mCancelSplitRunnable
: null)
.animateWithVelocity(velocity);
@@ -16,7 +16,6 @@
package com.android.launcher3.uioverrides.touchcontrollers;
import static android.view.MotionEvent.ACTION_DOWN;
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;
@@ -49,6 +48,7 @@ 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.DesktopTaskView.isDesktopModeSupported;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
@@ -83,7 +83,6 @@ import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.MotionPauseDetector;
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;
@@ -177,7 +176,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
return false;
}
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
if (isDesktopModeSupported()) {
// TODO(b/268075592): add support for quickswitch to/from desktop
return false;
}
@@ -15,8 +15,7 @@
*/
package com.android.launcher3.uioverrides.touchcontrollers;
import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
import static com.android.launcher3.AbstractFloatingView.TYPE_TOUCH_CONTROLLER_NO_INTERCEPT;
import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
@@ -84,7 +83,7 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
return false;
}
}
if (getTopOpenViewWithType(mLauncher, TYPE_ACCESSIBLE | TYPE_ALL_APPS_EDU) != null) {
if (getTopOpenViewWithType(mLauncher, TYPE_TOUCH_CONTROLLER_NO_INTERCEPT) != null) {
return false;
}
return true;
@@ -96,7 +95,7 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()
? mLauncher.getStateManager().getLastState()
: NORMAL;
} else if (fromState == NORMAL && isDragTowardPositive) {
} else if (fromState == NORMAL && shouldOpenAllApps(isDragTowardPositive)) {
return ALL_APPS;
}
return fromState;
@@ -30,6 +30,7 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PR
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
@@ -48,7 +49,6 @@ import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.NavigationMode;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -79,7 +79,7 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll
if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) {
return false;
}
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
if (isDesktopModeSupported()) {
// TODO(b/268075592): add support for quickswitch to/from desktop
return false;
}
@@ -21,6 +21,7 @@ import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
import static com.android.launcher3.MotionEventsUtils.isTrackpadScroll;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPE_DOWN_WORKSPACE_NOTISHADE_OPEN;
import android.graphics.PointF;
@@ -57,6 +58,8 @@ public class StatusBarTouchController implements TouchController {
/* If {@code false}, this controller should not handle the input {@link MotionEvent}.*/
private boolean mCanIntercept;
private boolean mIsTrackpadReverseScroll;
public StatusBarTouchController(Launcher l) {
mLauncher = l;
mSystemUiProxy = SystemUiProxy.INSTANCE.get(mLauncher);
@@ -92,6 +95,8 @@ public class StatusBarTouchController implements TouchController {
}
mDownEvents.clear();
mDownEvents.put(pid, new PointF(ev.getX(), ev.getY()));
mIsTrackpadReverseScroll = !mLauncher.isNaturalScrollingEnabled()
&& isTrackpadScroll(ev);
} else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
// Check!! should only set it only when threshold is not entered.
mDownEvents.put(pid, new PointF(ev.getX(idx), ev.getY(idx)));
@@ -102,6 +107,9 @@ public class StatusBarTouchController implements TouchController {
if (action == ACTION_MOVE) {
float dy = ev.getY(idx) - mDownEvents.get(pid).y;
float dx = ev.getX(idx) - mDownEvents.get(pid).x;
if (mIsTrackpadReverseScroll) {
dy = -dy;
}
// Currently input dispatcher will not do touch transfer if there are more than
// one touch pointer. Hence, even if slope passed, only set the slippery flag
// when there is single touch event. (context: InputDispatcher.cpp line 1445)
@@ -126,6 +134,7 @@ public class StatusBarTouchController implements TouchController {
mLauncher.getStatsLogManager().logger()
.log(LAUNCHER_SWIPE_DOWN_WORKSPACE_NOTISHADE_OPEN);
setWindowSlippery(false);
mIsTrackpadReverseScroll = false;
return true;
}
return true;
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.uioverrides.touchcontrollers;
import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
import static com.android.launcher3.AbstractFloatingView.TYPE_TOUCH_CONTROLLER_NO_INTERCEPT;
import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH;
@@ -112,7 +112,8 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
// If we are already animating from a previous state, we can intercept.
return true;
}
if (AbstractFloatingView.getTopOpenViewWithType(mActivity, TYPE_ACCESSIBLE) != null) {
if (AbstractFloatingView.getTopOpenViewWithType(
mActivity, TYPE_TOUCH_CONTROLLER_NO_INTERCEPT) != null) {
return false;
}
return isRecentsInteractive();
@@ -24,6 +24,8 @@ 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.EVENT_DESTROYED;
import static com.android.launcher3.BaseActivity.EVENT_STARTED;
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;
@@ -51,8 +53,10 @@ import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHE
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.INVALID_VELOCITY_ON_SWIPE_UP;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.LAUNCHER_DESTROYED;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET;
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -61,7 +65,6 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
@@ -78,6 +81,7 @@ import android.os.SystemClock;
import android.util.Log;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.View;
import android.view.View.OnApplyWindowInsetsListener;
import android.view.ViewGroup;
@@ -88,6 +92,7 @@ import android.view.animation.Interpolator;
import android.widget.Toast;
import android.window.PictureInPictureSurfaceTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -108,7 +113,6 @@ import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarThresholdUtils;
import com.android.launcher3.taskbar.TaskbarUIController;
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;
@@ -131,7 +135,6 @@ import com.android.quickstep.util.SurfaceTransaction;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.SwipePipToHomeAnimator;
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;
@@ -177,25 +180,19 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
protected @Nullable RecentsAnimationController mRecentsAnimationController;
protected @Nullable RecentsAnimationController mDeferredCleanupRecentsAnimationController;
protected RecentsAnimationTargets mRecentsAnimationTargets;
protected T mActivity;
protected @Nullable T mActivity;
protected @Nullable Q mRecentsView;
protected Runnable mGestureEndCallback;
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;
mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
}
};
private final Runnable mLauncherOnDestroyCallback = () -> {
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) {
@@ -316,8 +313,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
private final int mSplashMainWindowShiftLength;
private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
private final Runnable mLauncherOnStartCallback = this::onLauncherStart;
private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
@Nullable private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
protected boolean mIsSwipingPipToHome;
// TODO(b/195473090) no split PIP for now, remove once we have more clarity
// can try to have RectFSpringAnim evaluate multiple rects at once
@@ -355,7 +353,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
return ROTATION_0;
}
return mRecentsView.getPagedViewOrientedState().getRecentsActivityRotation();
}, inputConsumer, /* callback = */ () -> {
}, inputConsumer, /* onTouchDownCallback = */ () -> {
endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
endLauncherTransitionController();
}, new InputProxyHandlerFactory(mActivityInterface, mGestureState));
@@ -489,6 +487,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
return true;
}
resetLauncherListeners();
// The launcher may have been recreated as a result of device rotation.
int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES;
@@ -512,7 +511,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
if (alreadyOnHome) {
onLauncherStart();
} else {
activity.runOnceOnStart(this::onLauncherStart);
activity.addEventCallback(EVENT_STARTED, mLauncherOnStartCallback);
}
// Set up a entire animation lifecycle callback to notify the current recents view when
@@ -537,9 +536,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
setupRecentsViewUi();
mRecentsView.runOnPageScrollsInitialized(this::linkRecentsViewScroll);
activity.runOnBindToTouchInteractionService(this::onLauncherBindToService);
mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
mActivity.runOnBindToTouchInteractionService(this::onLauncherBindToService);
mActivity.addEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
return true;
}
@@ -552,7 +550,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
private void onLauncherStart() {
final T activity = mActivityInterface.getCreatedActivity();
if (mActivity != activity) {
if (activity == null || mActivity != activity) {
return;
}
if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
@@ -676,6 +674,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
if (Arrays.stream(runningTasks).anyMatch(Objects::isNull)) {
return;
}
if (mRecentsView == null) {
return;
}
mRecentsView.onGestureAnimationStart(runningTasks, mDeviceState.getRotationTouchHelper());
}
@@ -854,7 +855,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
if (windowInsets.isVisible(WindowInsets.Type.ime())) {
return result;
}
buildAnimationController();
if (mGestureState.getEndTarget() == null) {
buildAnimationController();
}
// Reapply the current shift to ensure it takes new insets into account, e.g. when long
// pressing to stash taskbar without moving the finger.
onCurrentShiftUpdated();
@@ -922,6 +925,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
// needs to be canceled
mRecentsAnimationController.setWillFinishToHome(swipeUpThresholdPassed);
if (mActivity == null) return;
if (swipeUpThresholdPassed) {
mActivity.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0);
} else {
@@ -935,7 +939,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
super.onRecentsAnimationStart(controller, targets);
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED && targets.hasDesktopTasks()) {
if (isDesktopModeSupported() && targets.hasDesktopTasks()) {
mRemoteTargetHandles = mTargetGluer.assignTargetsForDesktop(targets);
} else {
int untrimmedAppCount = mRemoteTargetHandles.length;
@@ -1161,7 +1165,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
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) {
if (isDesktopModeSupported()) {
// Notify the SysUI to stash desktop apps if they are visible
DesktopVisibilityController desktopVisibilityController =
mActivityInterface.getDesktopVisibilityController();
@@ -1189,7 +1193,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
break;
}
ActiveGestureLog.INSTANCE.addLog(
/* event= */ "onSettledOnEndTarget " + endTarget,
new ActiveGestureLog.CompoundString("onSettledOnEndTarget ")
.append(endTarget.name()),
/* gestureEvent= */ ON_SETTLED_ON_END_TARGET);
}
@@ -1213,14 +1218,19 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
private GestureEndTarget calculateEndTarget(
PointF velocityPxPerMs, float endVelocityPxPerMs, boolean isFlingY, boolean isCancel) {
ActiveGestureErrorDetector.GestureEvent gestureEvent =
velocityPxPerMs.x == 0 && velocityPxPerMs.y == 0
? INVALID_VELOCITY_ON_SWIPE_UP
: null;
ActiveGestureLog.INSTANCE.addLog(
new ActiveGestureLog.CompoundString("calculateEndTarget: velocities=(x=")
.append(Float.toString(dpiFromPx(velocityPxPerMs.x)))
.append(dpiFromPx(velocityPxPerMs.x))
.append("dp/ms, y=")
.append(Float.toString(dpiFromPx(velocityPxPerMs.y)))
.append(dpiFromPx(velocityPxPerMs.y))
.append("dp/ms), angle=")
.append(Double.toString(Math.toDegrees(Math.atan2(
-velocityPxPerMs.y, velocityPxPerMs.x)))));
.append(Math.toDegrees(Math.atan2(
-velocityPxPerMs.y, velocityPxPerMs.x))), gestureEvent);
if (mGestureState.isHandlingAtomicEvent()) {
// Button mode, this is only used to go to recents.
@@ -1240,7 +1250,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
return LAST_TASK;
}
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED && endTarget == NEW_TASK) {
if (isDesktopModeSupported() && endTarget == NEW_TASK) {
// TODO(b/268075592): add support for quickswitch to/from desktop
return LAST_TASK;
}
@@ -1257,8 +1267,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
return isSwipeUp ? ALL_APPS : LAST_TASK;
}
if (!isSwipeUp) {
final boolean isCenteredOnNewTask =
mRecentsView.getDestinationPage() != mRecentsView.getRunningTaskIndex();
final boolean isCenteredOnNewTask = mRecentsView != null
&& mRecentsView.getDestinationPage() != mRecentsView.getRunningTaskIndex();
return willGoToNewTask || isCenteredOnNewTask ? NEW_TASK : LAST_TASK;
}
@@ -1491,7 +1501,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
if (mGestureState.getEndTarget().isLauncher) {
// This is also called when the launcher is resumed, in order to clear the pending
// widgets that have yet to be configured.
DragView.removeAllViews(mActivity);
if (mActivity != null) {
DragView.removeAllViews(mActivity);
}
TaskStackChangeListeners.getInstance().registerTaskStackListener(
mActivityRestartListener);
@@ -1534,11 +1546,13 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
HomeAnimationFactory homeAnimFactory =
createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
runningTaskTarget);
mIsSwipingPipToHome = !mIsSwipeForSplit && appCanEnterPip;
SwipePipToHomeAnimator swipePipToHomeAnimator = !mIsSwipeForSplit && appCanEnterPip
? createWindowAnimationToPip(homeAnimFactory, runningTaskTarget, start)
: null;
mIsSwipingPipToHome = swipePipToHomeAnimator != null;
final RectFSpringAnim[] windowAnim;
if (mIsSwipingPipToHome) {
mSwipePipToHomeAnimator = createWindowAnimationToPip(
homeAnimFactory, runningTaskTarget, start);
mSwipePipToHomeAnimator = swipePipToHomeAnimator;
mSwipePipToHomeAnimators[0] = mSwipePipToHomeAnimator;
if (mSwipePipToHomeReleaseCheck != null) {
mSwipePipToHomeReleaseCheck.setCanRelease(false);
@@ -1546,6 +1560,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
// grab a screenshot before the PipContentOverlay gets parented on top of the task
UI_HELPER_EXECUTOR.execute(() -> {
if (mRecentsAnimationController == null) {
return;
}
// Directly use top task, split to pip handled on shell side
final int taskId = mGestureState.getTopRunningTaskId();
mTaskSnapshotCache.put(taskId,
@@ -1663,6 +1680,10 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
@Nullable
private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
RemoteAnimationTarget runningTaskTarget, float startProgress) {
if (mRecentsView == null) {
// Overview was destroyed, bail early.
return null;
}
// Directly animate the app to PiP (picture-in-picture) mode
final ActivityManager.RunningTaskInfo taskInfo = runningTaskTarget.taskInfo;
final RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
@@ -1803,7 +1824,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
private void continueComputingRecentsScrollIfNecessary() {
if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)
&& !mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)
&& !mCanceled) {
&& !mCanceled
&& mRecentsView != null) {
computeRecentsScrollIfInvisible();
mRecentsView.postOnAnimation(this::continueComputingRecentsScrollIfNecessary);
}
@@ -1844,12 +1866,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
}
public void onConsumerAboutToBeSwitched() {
if (mActivity != null) {
// In the off chance that the gesture ends before Launcher is started, we should clear
// the callback here so that it doesn't update with the wrong state
mActivity.clearRunOnceOnStartCallback();
resetLauncherListeners();
}
// In the off chance that the gesture ends before Launcher is started, we should clear
// the callback here so that it doesn't update with the wrong state
resetLauncherListeners();
if (mGestureState.isRecentsAnimationRunning() && mGestureState.getEndTarget() != null
&& !mGestureState.getEndTarget().isLauncher) {
// Continued quick switch.
@@ -1913,7 +1932,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
private void reset() {
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
if (mActivity != null) {
mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
mActivity.removeEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
}
}
@@ -1929,7 +1948,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
mCurrentShift.cancelAnimation();
// Cleanup when switching handlers
mInputConsumerProxy.unregisterCallback();
mInputConsumerProxy.unregisterOnTouchDownCallback();
mActivityInitListener.unregister();
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
mActivityRestartListener);
@@ -1941,7 +1960,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
mInputConsumerProxy.destroy();
mTaskAnimationManager.setLiveTileCleanUpHandler(null);
}
mInputConsumerProxy.unregisterCallback();
mInputConsumerProxy.unregisterOnTouchDownCallback();
endRunningWindowAnim(false /* cancel */);
if (mGestureEndCallback != null) {
@@ -1984,8 +2003,12 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
* continued quick switch gesture, which cancels the previous handler but doesn't invalidate it.
*/
private void resetLauncherListeners() {
mActivity.getRootView().setOnApplyWindowInsetsListener(null);
if (mActivity != null) {
mActivity.removeEventCallback(EVENT_STARTED, mLauncherOnStartCallback);
mActivity.removeEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
mActivity.getRootView().setOnApplyWindowInsetsListener(null);
}
if (mRecentsView != null) {
mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
}
@@ -2021,10 +2044,12 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
// Update the screenshot of the task
if (shouldUpdate) {
UI_HELPER_EXECUTOR.execute(() -> {
if (mRecentsAnimationController == null) return;
RecentsAnimationController recentsAnimationController =
mRecentsAnimationController;
if (recentsAnimationController == null) return;
for (int id : runningTaskIds) {
mTaskSnapshotCache.put(
id, mRecentsAnimationController.screenshotTask(id));
id, recentsAnimationController.screenshotTask(id));
}
MAIN_EXECUTOR.execute(() -> {
@@ -2077,18 +2102,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
}
private void finishCurrentTransitionToRecents() {
if (mRecentsView != null
&& mActivityInterface.getDesktopVisibilityController() != null
&& mActivityInterface.getDesktopVisibilityController().areFreeformTasksVisible()) {
mRecentsView.switchToScreenshot(() -> {
mRecentsView.finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
() -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
});
} else {
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
if (mRecentsAnimationController != null) {
mRecentsAnimationController.detachNavigationBarFromApp(true);
}
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
if (mRecentsAnimationController != null) {
mRecentsAnimationController.detachNavigationBarFromApp(true);
}
}
@@ -2114,7 +2130,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
* from Launcher to WM.
*/
private void maybeAbortSwipePipToHome() {
if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
if (mIsSwipingPipToHome && mSwipePipToHomeAnimator != null) {
SystemUiProxy.INSTANCE.get(mContext).abortSwipePipToHome(
mSwipePipToHomeAnimator.getTaskId(),
mSwipePipToHomeAnimator.getComponentName());
@@ -2128,7 +2144,10 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
* This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
*/
private void maybeFinishSwipePipToHome() {
if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
if (mRecentsAnimationController == null) {
return;
}
if (mIsSwipingPipToHome && mSwipePipToHomeAnimator != null) {
mRecentsAnimationController.setFinishTaskTransaction(
mSwipePipToHomeAnimator.getTaskId(),
mSwipePipToHomeAnimator.getFinishTransaction(),
@@ -2152,7 +2171,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
protected abstract void finishRecentsControllerToHome(Runnable callback);
private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED) || mRecentsView == null) {
return;
}
endLauncherTransitionController();
@@ -2186,6 +2205,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
}
protected void linkRecentsViewScroll() {
if (mRecentsView == null) {
return;
}
SurfaceTransactionApplier applier = new SurfaceTransactionApplier(mRecentsView);
runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
.setSyncTransactionApplier(applier));
@@ -2193,9 +2215,13 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
mRecentsAnimationTargets.addReleaseCheck(applier));
mRecentsView.addOnScrollChangedListener(mOnRecentsScrollListener);
runOnRecentsAnimationAndLauncherBound(() ->
mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
mRecentsAnimationTargets));
runOnRecentsAnimationAndLauncherBound(() -> {
if (mRecentsView == null) {
return;
}
mRecentsView.setRecentsAnimationTargets(
mRecentsAnimationController, mRecentsAnimationTargets);
});
// Disable scrolling in RecentsView for trackpad 3-finger swipe up gesture.
if (!mGestureState.isThreeFingerTrackpadGesture()) {
@@ -2215,7 +2241,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
TaskView nextTask = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
if (nextTask != null) {
int[] taskIds = nextTask.getTaskIds();
StringBuilder nextTaskLog = new StringBuilder();
ActiveGestureLog.CompoundString nextTaskLog = new ActiveGestureLog.CompoundString(
"Launching task: ");
for (TaskIdAttributeContainer c : nextTask.getTaskIdAttributeContainers()) {
if (c == null) {
continue;
@@ -2234,7 +2261,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
if (!hasTaskPreviouslyAppeared) {
ActiveGestureLog.INSTANCE.trackEvent(EXPECTING_TASK_APPEARED);
}
ActiveGestureLog.INSTANCE.addLog("Launching task: " + nextTaskLog);
ActiveGestureLog.INSTANCE.addLog(nextTaskLog);
nextTask.launchTask(success -> {
resultCallback.accept(success);
if (success) {
@@ -2292,7 +2319,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
}
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
public void onRecentsAnimationFinished(@NonNull RecentsAnimationController controller) {
mRecentsAnimationController = null;
mRecentsAnimationTargets = null;
if (mRecentsView != null) {
@@ -2301,77 +2328,95 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
}
@Override
public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
if (mRecentsAnimationController != null) {
boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTargets).anyMatch(
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
// previous quickswitch task launch, then cancel the animation back to the app
RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
TaskInfo taskInfo = appearedTaskTarget.taskInfo;
ActiveGestureLog.INSTANCE.addLog("Unexpected task appeared"
+ " id=" + taskInfo.taskId
+ " pkg=" + taskInfo.baseIntent.getComponent().getPackageName());
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
} else if (handleTaskAppeared(appearedTaskTargets)) {
Optional<RemoteAnimationTarget> taskTargetOptional =
Arrays.stream(appearedTaskTargets)
.filter(mGestureState.mLastStartedTaskIdPredicate)
.findFirst();
if (!taskTargetOptional.isPresent()) {
ActiveGestureLog.INSTANCE.addLog("No appeared task matching started task id");
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
}
RemoteAnimationTarget taskTarget = taskTargetOptional.get();
TaskView taskView = mRecentsView == null
? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
if (taskView == null || !taskView.getThumbnail().shouldShowSplashView()) {
ActiveGestureLog.INSTANCE.addLog("Invalid task view splash state");
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
}
ViewGroup splashView = mActivity.getDragLayer();
final QuickstepLauncher quickstepLauncher = mActivity instanceof QuickstepLauncher
? (QuickstepLauncher) mActivity : null;
if (quickstepLauncher != null) {
quickstepLauncher.getDepthController().pauseBlursOnWindows(true);
}
// When revealing the app with launcher splash screen, make the app visible
// and behind the splash view before the splash is animated away.
SurfaceTransactionApplier surfaceApplier =
new SurfaceTransactionApplier(splashView);
SurfaceTransaction transaction = new SurfaceTransaction();
for (RemoteAnimationTarget target : appearedTaskTargets) {
transaction.forSurface(target.leash).setAlpha(1).setLayer(-1).setShow();
}
surfaceApplier.scheduleApply(transaction);
SplashScreenExitAnimationUtils.startAnimations(splashView, taskTarget.leash,
mSplashMainWindowShiftLength, new TransactionPool(), new Rect(),
SPLASH_ANIMATION_DURATION, SPLASH_FADE_OUT_DURATION,
/* iconStartAlpha= */ 0, /* brandingStartAlpha= */ 0,
SPLASH_APP_REVEAL_DELAY, SPLASH_APP_REVEAL_DURATION,
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Hiding launcher which shows the app surface behind, then
// finishing recents to the app. After transition finish, showing
// the views on launcher again, so it can be visible when next
// animation starts.
splashView.setAlpha(0);
if (quickstepLauncher != null) {
quickstepLauncher.getDepthController()
.pauseBlursOnWindows(false);
}
finishRecentsAnimationOnTasksAppeared(() -> splashView.setAlpha(1));
}
});
}
public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets) {
if (mRecentsAnimationController == null) {
return;
}
boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTargets).anyMatch(
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
// previous quickswitch task launch, then cancel the animation back to the app
RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
TaskInfo taskInfo = appearedTaskTarget.taskInfo;
ActiveGestureLog.INSTANCE.addLog(
new ActiveGestureLog.CompoundString("Unexpected task appeared")
.append(" id=")
.append(taskInfo.taskId)
.append(" pkg=")
.append(taskInfo.baseIntent.getComponent().getPackageName()));
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
}
if (!handleTaskAppeared(appearedTaskTargets)) {
return;
}
Optional<RemoteAnimationTarget> taskTargetOptional =
Arrays.stream(appearedTaskTargets)
.filter(mGestureState.mLastStartedTaskIdPredicate)
.findFirst();
if (!taskTargetOptional.isPresent()) {
ActiveGestureLog.INSTANCE.addLog("No appeared task matching started task id");
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
}
RemoteAnimationTarget taskTarget = taskTargetOptional.get();
TaskView taskView = mRecentsView == null
? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
if (taskView == null || !taskView.getThumbnail().shouldShowSplashView()) {
ActiveGestureLog.INSTANCE.addLog("Invalid task view splash state");
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
}
if (mActivity == null) {
ActiveGestureLog.INSTANCE.addLog("Activity destroyed");
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
}
animateSplashScreenExit(mActivity, appearedTaskTargets, taskTarget.leash);
}
private void animateSplashScreenExit(
@NonNull T activity,
@NonNull RemoteAnimationTarget[] appearedTaskTargets,
@NonNull SurfaceControl leash) {
ViewGroup splashView = activity.getDragLayer();
final QuickstepLauncher quickstepLauncher = activity instanceof QuickstepLauncher
? (QuickstepLauncher) activity : null;
if (quickstepLauncher != null) {
quickstepLauncher.getDepthController().pauseBlursOnWindows(true);
}
// When revealing the app with launcher splash screen, make the app visible
// and behind the splash view before the splash is animated away.
SurfaceTransactionApplier surfaceApplier =
new SurfaceTransactionApplier(splashView);
SurfaceTransaction transaction = new SurfaceTransaction();
for (RemoteAnimationTarget target : appearedTaskTargets) {
transaction.forSurface(target.leash).setAlpha(1).setLayer(-1).setShow();
}
surfaceApplier.scheduleApply(transaction);
SplashScreenExitAnimationUtils.startAnimations(splashView, leash,
mSplashMainWindowShiftLength, new TransactionPool(), new Rect(),
SPLASH_ANIMATION_DURATION, SPLASH_FADE_OUT_DURATION,
/* iconStartAlpha= */ 0, /* brandingStartAlpha= */ 0,
SPLASH_APP_REVEAL_DELAY, SPLASH_APP_REVEAL_DURATION,
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Hiding launcher which shows the app surface behind, then
// finishing recents to the app. After transition finish, showing
// the views on launcher again, so it can be visible when next
// animation starts.
splashView.setAlpha(0);
if (quickstepLauncher != null) {
quickstepLauncher.getDepthController()
.pauseBlursOnWindows(false);
}
finishRecentsAnimationOnTasksAppeared(() -> splashView.setAlpha(1));
}
});
}
private void finishRecentsAnimationOnTasksAppeared(Runnable onFinishComplete) {
@@ -25,6 +25,7 @@ import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
@@ -50,10 +51,10 @@ import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.BaseState;
@@ -65,7 +66,6 @@ import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.views.ScrimView;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -111,7 +111,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
if (endTarget != null) {
// We were on our way to this state when we got canceled, end there instead.
startState = stateFromGestureEndTarget(endTarget);
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
if (isDesktopModeSupported()) {
DesktopVisibilityController controller = getDesktopVisibilityController();
if (controller != null && controller.areFreeformTasksVisible()
&& endTarget == LAST_TASK) {
@@ -191,8 +191,11 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
public abstract boolean allowAllAppsFromOverview();
public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
TaskbarUIController controller = getTaskbarController();
boolean isEventOverBubbleBarStashHandle =
controller != null && controller.isEventOverBubbleBarStashHandle(ev);
return deviceState.isInDeferredGestureRegion(ev) || deviceState.isImeRenderingNavButtons()
|| isTrackpadMultiFingerSwipe(ev);
|| isTrackpadMultiFingerSwipe(ev) || isEventOverBubbleBarStashHandle;
}
/**
@@ -239,7 +242,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
PagedOrientationHandler orientedState) {
if (dp.isTablet) {
if (FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) {
if (Flags.enableGridOnlyOverview()) {
calculateGridTaskSize(context, dp, outRect, orientedState);
} else {
calculateFocusTaskSize(context, dp, outRect);
@@ -264,7 +267,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
Resources res = context.getResources();
float maxScale = res.getFloat(R.dimen.overview_max_scale);
Rect gridRect = new Rect();
calculateGridSize(dp, gridRect);
calculateGridSize(dp, context, gridRect);
calculateTaskSizeInternal(context, dp, gridRect, maxScale, Gravity.CENTER, outRect);
}
@@ -318,10 +321,16 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
/**
* Calculates the overview grid size for the provided device configuration.
*/
public final void calculateGridSize(DeviceProfile dp, Rect outRect) {
public final void calculateGridSize(DeviceProfile dp, Context context, Rect outRect) {
Rect insets = dp.getInsets();
int topMargin = dp.overviewTaskThumbnailTopMarginPx;
int bottomMargin = dp.getOverviewActionsClaimedSpace();
if (dp.isTaskbarPresent && Flags.enableGridOnlyOverview()) {
topMargin += context.getResources().getDimensionPixelSize(
R.dimen.overview_top_margin_grid_only);
bottomMargin += context.getResources().getDimensionPixelSize(
R.dimen.overview_bottom_margin_grid_only);
}
int sideMargin = dp.overviewGridSideMargin;
outRect.set(0, 0, dp.widthPx, dp.heightPx);
@@ -336,8 +345,8 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
PagedOrientationHandler orientedState) {
Resources res = context.getResources();
Rect potentialTaskRect = new Rect();
if (FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) {
calculateGridSize(dp, potentialTaskRect);
if (Flags.enableGridOnlyOverview()) {
calculateGridSize(dp, context, potentialTaskRect);
} else {
calculateFocusTaskSize(context, dp, potentialTaskRect);
}
@@ -368,7 +377,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect,
PagedOrientationHandler orientedState) {
calculateTaskSize(context, dp, outRect, orientedState);
boolean isGridOnlyOverview = dp.isTablet && FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get();
boolean isGridOnlyOverview = dp.isTablet && Flags.enableGridOnlyOverview();
int claimedSpaceBelow = isGridOnlyOverview
? dp.overviewActionsTopMarginPx + dp.overviewActionsHeight + dp.stashedTaskbarHeight
: (dp.heightPx - outRect.bottom - dp.getInsets().bottom);
@@ -437,11 +446,13 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
}
protected void runOnInitBackgroundStateUI(Runnable callback) {
mOnInitBackgroundStateUICallback = callback;
ACTIVITY_TYPE activity = getCreatedActivity();
if (activity != null && activity.getStateManager().getState() == mBackgroundState) {
callback.run();
onInitBackgroundStateUI();
return;
}
mOnInitBackgroundStateUICallback = callback;
}
private void onInitBackgroundStateUI() {
@@ -19,7 +19,7 @@ import android.content.Context
import android.util.Log
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.isBootAwareStartupDataEnabled
import com.android.launcher3.moveStartupDataToDeviceProtectedStorageIsEnabled
import com.android.launcher3.util.LockedUserState
/**
@@ -33,7 +33,8 @@ object BootAwarePreloader {
fun start(context: Context) {
val lp = LauncherPrefs.get(context)
when {
LockedUserState.get(context).isUserUnlocked || !isBootAwareStartupDataEnabled -> {
LockedUserState.get(context).isUserUnlocked ||
!moveStartupDataToDeviceProtectedStorageIsEnabled -> {
/* No-Op */
}
lp.isStartupDataMigrated -> {
@@ -66,6 +66,7 @@ import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
import com.android.quickstep.util.TransformParams;
@@ -151,19 +152,20 @@ public class FallbackSwipeHandler extends
return new FallbackPipToHomeAnimationFactory();
}
mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
startHomeIntent(mActiveAnimationFactory, runningTaskTarget);
startHomeIntent(mActiveAnimationFactory, runningTaskTarget, "FallbackSwipeHandler-home");
return mActiveAnimationFactory;
}
private void startHomeIntent(
@Nullable FallbackHomeAnimationFactory gestureContractAnimationFactory,
@Nullable RemoteAnimationTarget runningTaskTarget) {
@Nullable RemoteAnimationTarget runningTaskTarget,
@NonNull String reason) {
ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
Intent intent = new Intent(mGestureState.getHomeIntent());
if (gestureContractAnimationFactory != null && runningTaskTarget != null) {
gestureContractAnimationFactory.addGestureContract(intent, runningTaskTarget.taskInfo);
}
startHomeIntentSafely(mContext, intent, options.toBundle());
startHomeIntentSafely(mContext, intent, options.toBundle(), reason);
}
@Override
@@ -185,8 +187,8 @@ public class FallbackSwipeHandler extends
// the PiP task appearing.
recentsCallback = () -> {
callback.run();
startHomeIntent(
null /* gestureContractAnimationFactory */, null /* runningTaskTarget */);
startHomeIntent(null /* gestureContractAnimationFactory */,
null /* runningTaskTarget */, "FallbackSwipeHandler-resumeLauncher");
};
} else {
recentsCallback = callback;
@@ -29,13 +29,15 @@ import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_HOME;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_NEW_TASK;
import android.annotation.Nullable;
import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Build;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
@@ -303,6 +305,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
/**
* @return the running task for this gesture.
*/
@Nullable
public CachedTaskInfo getRunningTask() {
return mRunningTask;
}
@@ -336,7 +339,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
/**
* Updates the running task for the gesture to be the given {@param runningTask}.
*/
public void updateRunningTask(CachedTaskInfo runningTask) {
public void updateRunningTask(@NonNull CachedTaskInfo runningTask) {
mRunningTask = runningTask;
}
@@ -410,7 +413,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
mEndTarget = target;
mStateCallback.setState(STATE_END_TARGET_SET);
ActiveGestureLog.INSTANCE.addLog(
/* event= */ "setEndTarget " + mEndTarget,
new ActiveGestureLog.CompoundString("setEndTarget ")
.append(mEndTarget.name()),
/* gestureEvent= */ SET_END_TARGET);
switch (mEndTarget) {
case HOME:
@@ -126,4 +126,14 @@ public interface InputConsumer {
}
return name.toString();
}
/**
* Returns an input consumer of the given class type, or null if none is found.
*/
default <T extends InputConsumer> T getInputConsumerOfClass(Class<T> c) {
if (getClass().equals(c)) {
return c.cast(this);
}
return null;
}
}
@@ -276,8 +276,11 @@ public final class LauncherActivityInterface extends
@Override
public boolean isInLiveTileMode() {
Launcher launcher = getCreatedActivity();
return launcher != null && launcher.getStateManager().getState() == OVERVIEW &&
launcher.isStarted();
return launcher != null
&& launcher.getStateManager().getState() == OVERVIEW
&& launcher.isStarted()
&& TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false).isHomeTask();
}
@Override
@@ -23,6 +23,8 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -43,6 +45,7 @@ import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.window.BackEvent;
import android.window.BackMotionEvent;
@@ -51,13 +54,17 @@ import android.window.IOnBackInvokedCallback;
import com.android.internal.view.AppearanceRegion;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.systemui.shared.system.QuickStepContract;
import java.lang.ref.WeakReference;
/**
* Controls the animation of swiping back and returning to launcher.
*
@@ -97,6 +104,7 @@ public class LauncherBackAnimationController {
private final float mWindowScaleEndCornerRadius;
private final float mWindowScaleStartCornerRadius;
private final Interpolator mCancelInterpolator;
private final Interpolator mProgressInterpolator = new DecelerateInterpolator();
private final PointF mInitialTouchPos = new PointF();
private RemoteAnimationTarget mBackTarget;
@@ -105,7 +113,7 @@ public class LauncherBackAnimationController {
private boolean mAnimatorSetInProgress = false;
private float mBackProgress = 0;
private boolean mBackInProgress = false;
private IOnBackInvokedCallback mBackCallback;
private OnBackInvokedCallbackStub mBackCallback;
private IRemoteAnimationFinishedCallback mAnimationFinishedCallback;
private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
private SurfaceControl mScrimLayer;
@@ -137,65 +145,113 @@ 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 onBackCancelled() {
handler.post(() -> {
mBackCallback = new OnBackInvokedCallbackStub(handler, mProgressAnimator,
mProgressInterpolator, this);
SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(mBackCallback,
new RemoteAnimationRunnerStub(this));
}
private static class OnBackInvokedCallbackStub extends IOnBackInvokedCallback.Stub {
private Handler mHandler;
private BackProgressAnimator mProgressAnimator;
private final Interpolator mProgressInterpolator;
// LauncherBackAnimationController has strong reference to Launcher activity, the binder
// callback should not hold strong reference to it to avoid memory leak.
private WeakReference<LauncherBackAnimationController> mControllerRef;
private OnBackInvokedCallbackStub(
Handler handler,
BackProgressAnimator progressAnimator,
Interpolator progressInterpolator,
LauncherBackAnimationController controller) {
mHandler = handler;
mProgressAnimator = progressAnimator;
mProgressInterpolator = progressInterpolator;
mControllerRef = new WeakReference<>(controller);
}
@Override
public void onBackCancelled() {
mHandler.post(() -> {
LauncherBackAnimationController controller = mControllerRef.get();
if (controller != null) {
mProgressAnimator.onBackCancelled(
LauncherBackAnimationController.this::resetPositionAnimated);
});
}
@Override
public void onBackInvoked() {
handler.post(() -> {
startTransition();
mProgressAnimator.reset();
});
}
@Override
public void onBackProgressed(BackMotionEvent backEvent) {
handler.post(() -> {
mProgressAnimator.onBackProgressed(backEvent);
});
}
@Override
public void onBackStarted(BackMotionEvent backEvent) {
handler.post(() -> {
startBack(backEvent);
mProgressAnimator.onBackStarted(backEvent, event -> {
mBackProgress = event.getProgress();
// TODO: Update once the interpolation curve spec is finalized.
mBackProgress =
1 - (1 - mBackProgress) * (1 - mBackProgress) * (1
- mBackProgress);
updateBackProgress(mBackProgress, event);
});
});
}
};
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;
}
controller::resetPositionAnimated);
}
mAnimationFinishedCallback = finishedCallback;
});
}
@Override
public void onBackInvoked() {
mHandler.post(() -> {
LauncherBackAnimationController controller = mControllerRef.get();
if (controller != null) {
controller.startTransition();
}
mProgressAnimator.reset();
});
}
@Override
public void onBackProgressed(BackMotionEvent backMotionEvent) {
mHandler.post(() -> {
LauncherBackAnimationController controller = mControllerRef.get();
if (controller == null
|| controller.mLauncher == null
|| !controller.mLauncher.isStarted()) {
// Skip animating back progress if Launcher isn't visible yet.
return;
}
mProgressAnimator.onBackProgressed(backMotionEvent);
});
}
@Override
public void onBackStarted(BackMotionEvent backEvent) {
mHandler.post(() -> {
LauncherBackAnimationController controller = mControllerRef.get();
if (controller != null) {
controller.startBack(backEvent);
mProgressAnimator.onBackStarted(backEvent, event -> {
float backProgress = event.getProgress();
controller.mBackProgress =
mProgressInterpolator.getInterpolation(backProgress);
controller.updateBackProgress(controller.mBackProgress, event);
});
}
});
}
}
private static class RemoteAnimationRunnerStub extends IRemoteAnimationRunner.Stub {
// LauncherBackAnimationController has strong reference to Launcher activity, the binder
// callback should not hold strong reference to it to avoid memory leak.
private WeakReference<LauncherBackAnimationController> mControllerRef;
private RemoteAnimationRunnerStub(LauncherBackAnimationController controller) {
mControllerRef = new WeakReference<>(controller);
}
@Override
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) {
LauncherBackAnimationController controller = mControllerRef.get();
if (controller == null) {
return;
}
for (final RemoteAnimationTarget target : apps) {
if (MODE_CLOSING == target.mode) {
controller.mBackTarget = target;
break;
}
}
controller.mAnimationFinishedCallback = finishedCallback;
}
@Override
public void onAnimationCancelled() {}
};
SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(mBackCallback, runner);
@Override
public void onAnimationCancelled() {}
}
private void resetPositionAnimated() {
@@ -241,8 +297,12 @@ public class LauncherBackAnimationController {
mBackTarget = appTarget;
mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
// TODO(b/218916755): Offset start rectangle in multiwindow mode.
mStartRect.set(appTarget.windowConfiguration.getMaxBounds());
if (mLauncher.getDeviceProfile().isTaskbarPresent && enableTaskbarPinning()
&& LauncherPrefs.get(mLauncher).get(TASKBAR_PINNING)) {
int insetBottom = mStartRect.bottom - appTarget.contentInsets.bottom;
mStartRect.set(mStartRect.left, mStartRect.top, mStartRect.right, insetBottom);
}
mCurrentRect.set(mStartRect);
addScrimLayer();
mTransaction.apply();
@@ -253,6 +313,10 @@ public class LauncherBackAnimationController {
SurfaceControl parent = viewRootImpl != null
? viewRootImpl.getSurfaceControl()
: null;
if (parent == null || !parent.isValid()) {
// Parent surface is not ready at the moment. Retry later.
return;
}
boolean isDarkTheme = Utilities.isDarkTheme(mLauncher);
mScrimLayer = new SurfaceControl.Builder()
.setName("Back to launcher background scrim")
@@ -285,13 +349,24 @@ public class LauncherBackAnimationController {
if (!mBackInProgress || mBackTarget == null) {
return;
}
if (mScrimLayer == null) {
// Scrim hasn't been attached yet. Let's attach it.
addScrimLayer();
}
float screenWidth = mStartRect.width();
float screenHeight = mStartRect.height();
float width = Utilities.mapRange(progress, 1, MIN_WINDOW_SCALE) * screenWidth;
float height = screenHeight / screenWidth * width;
float deltaYRatio = (event.getTouchY() - mInitialTouchPos.y) / screenHeight;
// Base the window movement in the Y axis on the touch movement in the Y axis.
float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * mWindowMaxDeltaY * progress;
float rawYDelta = event.getTouchY() - mInitialTouchPos.y;
float yDirection = rawYDelta < 0 ? -1 : 1;
// limit yDelta interpretation to 1/2 of screen height in either direction
float deltaYRatio = Math.min(screenHeight / 2f, Math.abs(rawYDelta)) / (screenHeight / 2f);
float interpolatedYRatio = mProgressInterpolator.getInterpolation(deltaYRatio);
// limit y-shift so surface never passes 8dp screen margin
float deltaY = yDirection * interpolatedYRatio * Math.max(0f, (screenHeight - height)
/ 2f - mWindowScaleMarginX);
// Move the window along the Y axis.
float top = (screenHeight - height) * 0.5f + deltaY;
// Move the window along the X axis.
@@ -349,6 +424,10 @@ public class LauncherBackAnimationController {
if (mLauncher.isDestroyed()) {
return;
}
LauncherTaskbarUIController taskbarUIController = mLauncher.getTaskbarUIController();
if (taskbarUIController != null) {
taskbarUIController.onLauncherVisibilityChanged(true);
}
// TODO: Catch the moment when launcher becomes visible after the top app un-occludes
// launcher and start animating afterwards. Currently we occasionally get a flicker from
// animating when launcher is still invisible.
@@ -363,12 +442,16 @@ public class LauncherBackAnimationController {
AbstractFloatingView.closeAllOpenViewsExcept(mLauncher, false, TYPE_REBIND_SAFE);
float cornerRadius = Utilities.mapRange(
mBackProgress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius);
final RectF resolveRectF = new RectF();
mQuickstepTransitionManager.transferRectToTargetCoordinate(
mBackTarget, mCurrentRect, true, resolveRectF);
Pair<RectFSpringAnim, AnimatorSet> pair =
mQuickstepTransitionManager.createWallpaperOpenAnimations(
new RemoteAnimationTarget[]{mBackTarget},
new RemoteAnimationTarget[0],
false /* fromUnlock */,
mCurrentRect,
resolveRectF,
cornerRadius,
mBackInProgress /* fromPredictiveBack */);
startTransitionAnimations(pair.first, pair.second);
@@ -401,6 +484,9 @@ public class LauncherBackAnimationController {
mScrimAlphaAnimator.cancel();
mScrimAlphaAnimator = null;
}
if (mScrimLayer != null) {
removeScrimLayer();
}
}
private void startTransitionAnimations(RectFSpringAnim springAnim, AnimatorSet anim) {
@@ -424,10 +510,14 @@ public class LauncherBackAnimationController {
tryFinishBackAnimation();
}
});
if (mScrimLayer == null) {
// Scrim hasn't been attached yet. Let's attach it.
addScrimLayer();
}
mScrimAlphaAnimator = new ValueAnimator().ofFloat(1, 0);
mScrimAlphaAnimator.addUpdateListener(animation -> {
float value = (Float) animation.getAnimatedValue();
if (mScrimLayer.isValid()) {
if (mScrimLayer != null && mScrimLayer.isValid()) {
mTransaction.setAlpha(mScrimLayer, value * mScrimAlpha);
mTransaction.apply();
}
@@ -0,0 +1,136 @@
package com.android.quickstep
import android.app.backup.BackupManager
import android.app.backup.BackupRestoreEventLogger
import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType
import android.app.backup.BackupRestoreEventLogger.BackupRestoreError
import android.content.Context
import com.android.launcher3.Flags
import com.android.launcher3.LauncherSettings.Favorites
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger
/**
* Concrete implementation for wrapper to log Restore event metrics for both success and failure to
* restore Launcher workspace from a backup. This implementation accesses SystemApis so is only
* available to QuickStep/NexusLauncher.
*/
class LauncherRestoreEventLoggerImpl(val context: Context) : LauncherRestoreEventLogger() {
companion object {
const val TAG = "LauncherRestoreEventLoggerImpl"
// Generic type for any possible workspace items, when specific type is not known.
@BackupRestoreDataType private const val DATA_TYPE_LAUNCHER_ITEM = "launcher_item"
// Specific workspace item types, based off of Favorites Table.
@BackupRestoreDataType private const val DATA_TYPE_APPLICATION = "application"
@BackupRestoreDataType private const val DATA_TYPE_FOLDER = "folder"
@BackupRestoreDataType private const val DATA_TYPE_APPWIDGET = "widget"
@BackupRestoreDataType private const val DATA_TYPE_CUSTOM_APPWIDGET = "custom_widget"
@BackupRestoreDataType private const val DATA_TYPE_DEEP_SHORTCUT = "deep_shortcut"
@BackupRestoreDataType private const val DATA_TYPE_APP_PAIR = "app_pair"
}
private val backupManager: BackupManager = BackupManager(context)
private val restoreEventLogger: BackupRestoreEventLogger = backupManager.delayedRestoreLogger
/**
* For logging when multiple items of a given data type failed to restore.
*
* @param dataType The data type that was not restored.
* @param count the number of data items that were not restored.
* @param error error type for why the data was not restored.
*/
override fun logLauncherItemsRestoreFailed(
@BackupRestoreDataType dataType: String,
count: Int,
@BackupRestoreError error: String?
) {
if (Flags.enableLauncherBrMetrics()) {
restoreEventLogger.logItemsRestoreFailed(dataType, count, error)
}
}
/**
* For logging when multiple items of a given data type were successfully restored.
*
* @param dataType The data type that was restored.
* @param count the number of data items restored.
*/
override fun logLauncherItemsRestored(@BackupRestoreDataType dataType: String, count: Int) {
if (Flags.enableLauncherBrMetrics()) {
restoreEventLogger.logItemsRestored(dataType, count)
}
}
/**
* Helper to log successfully restoring a single item from the Favorites table.
*
* @param favoritesId The id of the item type from [Favorites] that was restored.
*/
override fun logSingleFavoritesItemRestored(favoritesId: Int) {
if (Flags.enableLauncherBrMetrics()) {
restoreEventLogger.logItemsRestored(favoritesIdToDataType(favoritesId), 1)
}
}
/**
* Helper to log a failure to restore a single item from the Favorites table.
*
* @param favoritesId The id of the item type from [Favorites] that was not restored.
* @param error error type for why the data was not restored.
*/
override fun logSingleFavoritesItemRestoreFailed(
favoritesId: Int,
@BackupRestoreError error: String?
) {
if (Flags.enableLauncherBrMetrics()) {
restoreEventLogger.logItemsRestoreFailed(favoritesIdToDataType(favoritesId), 1, error)
}
}
/**
* Helper to log a failure to restore items from the Favorites table.
*
* @param favoritesId The id of the item type from [Favorites] that was not restored.
* @param count number of items that failed to restore.
* @param error error type for why the data was not restored.
*/
override fun logFavoritesItemsRestoreFailed(
favoritesId: Int,
count: Int,
@BackupRestoreError error: String?
) {
if (Flags.enableLauncherBrMetrics()) {
restoreEventLogger.logItemsRestoreFailed(
favoritesIdToDataType(favoritesId),
count,
error
)
}
}
/**
* Uses the current [restoreEventLogger] to report its results to the [backupManager]. Use when
* done restoring items for Launcher.
*/
override fun reportLauncherRestoreResults() {
if (Flags.enableLauncherBrMetrics()) {
backupManager.reportDelayedRestoreResult(restoreEventLogger)
}
}
/**
* Helper method to convert item types from [Favorites] to B&R data types for logging. Also to
* avoid direct usage of @BackupRestoreDataType which is protected under @SystemApi.
*/
@BackupRestoreDataType
private fun favoritesIdToDataType(favoritesId: Int): String =
when (favoritesId) {
Favorites.ITEM_TYPE_APPLICATION -> DATA_TYPE_APPLICATION
Favorites.ITEM_TYPE_FOLDER -> DATA_TYPE_FOLDER
Favorites.ITEM_TYPE_APPWIDGET -> DATA_TYPE_APPWIDGET
Favorites.ITEM_TYPE_CUSTOM_APPWIDGET -> DATA_TYPE_CUSTOM_APPWIDGET
Favorites.ITEM_TYPE_DEEP_SHORTCUT -> DATA_TYPE_DEEP_SHORTCUT
Favorites.ITEM_TYPE_APP_PAIR -> DATA_TYPE_APP_PAIR
else -> DATA_TYPE_LAUNCHER_ITEM
}
}
@@ -37,6 +37,7 @@ import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -78,6 +79,14 @@ public class OverviewCommandHelper {
*/
private int mTaskFocusIndexOverride = -1;
/**
* Whether we should incoming toggle commands while a previous toggle command is still ongoing.
* This serves as a rate-limiter to prevent overlapping animations that can clobber each other
* and prevent clean-up callbacks from running. This thus prevents a recurring set of bugs with
* janky recents animations and unresponsive home and overview buttons.
*/
private boolean mWaitForToggleCommandComplete = false;
public OverviewCommandHelper(TouchInteractionService service,
OverviewComponentObserver observer,
TaskAnimationManager taskAnimationManager) {
@@ -160,15 +169,20 @@ public class OverviewCommandHelper {
private boolean launchTask(RecentsView recents, @Nullable TaskView taskView, CommandInfo cmd) {
RunnableList callbackList = null;
if (taskView != null) {
mWaitForToggleCommandComplete = true;
taskView.setEndQuickswitchCuj(true);
callbackList = taskView.launchTasks();
}
if (callbackList != null) {
callbackList.add(() -> scheduleNextTask(cmd));
callbackList.add(() -> {
scheduleNextTask(cmd);
mWaitForToggleCommandComplete = false;
});
return false;
} else {
recents.startHome();
mWaitForToggleCommandComplete = false;
return true;
}
}
@@ -178,6 +192,9 @@ public class OverviewCommandHelper {
* task is deferred until {@link #scheduleNextTask} is called
*/
private <T extends StatefulActivity<?>> boolean executeCommand(CommandInfo cmd) {
if (mWaitForToggleCommandComplete && cmd.type == TYPE_TOGGLE) {
return true;
}
BaseActivityInterface<?, T> activityInterface =
mOverviewComponentObserver.getActivityInterface();
RecentsView recents = activityInterface.getVisibleRecentsView();
@@ -204,6 +221,7 @@ public class OverviewCommandHelper {
return true;
}
if (cmd.type == TYPE_HOME) {
ActiveGestureLog.INSTANCE.addLog("OverviewCommandHelper.executeCommand(TYPE_HOME)");
mService.startActivity(mOverviewComponentObserver.getHomeIntent());
return true;
}
@@ -359,6 +377,7 @@ public class OverviewCommandHelper {
pw.println(" pendingCommandType=" + mPendingCommands.get(0).type);
}
pw.println(" mTaskFocusIndexOverride=" + mTaskFocusIndexOverride);
pw.println(" mWaitForToggleCommandComplete=" + mWaitForToggleCommandComplete);
}
private static class CommandInfo {
@@ -39,6 +39,7 @@ import androidx.annotation.Nullable;
import com.android.launcher3.R;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.systemui.shared.system.PackageManagerWrapper;
import java.io.PrintWriter;
@@ -276,20 +277,24 @@ public final class OverviewComponentObserver {
/**
* Starts the intent for the current home activity.
*/
public static void startHomeIntentSafely(@NonNull Context context, @Nullable Bundle options) {
public static void startHomeIntentSafely(@NonNull Context context, @Nullable Bundle options,
@NonNull String reason) {
RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(context);
OverviewComponentObserver observer = new OverviewComponentObserver(context, deviceState);
Intent intent = observer.getHomeIntent();
observer.onDestroy();
deviceState.destroy();
startHomeIntentSafely(context, intent, options);
startHomeIntentSafely(context, intent, options, reason);
}
/**
* Starts the intent for the current home activity.
*/
public static void startHomeIntentSafely(
@NonNull Context context, @NonNull Intent homeIntent, @Nullable Bundle options) {
@NonNull Context context, @NonNull Intent homeIntent, @Nullable Bundle options,
@NonNull String reason) {
ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
"OverviewComponentObserver.startHomeIntent: ").append(reason));
try {
context.startActivity(homeIntent, options);
} catch (NullPointerException | ActivityNotFoundException | SecurityException e) {

Some files were not shown because too many files have changed in this diff Show More