diff options
author | Xin Li <delphij@google.com> | 2024-03-07 06:41:07 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2024-03-07 06:41:07 +0000 |
commit | 17dabff1dd38e8f0e08736176a8a587e039e610a (patch) | |
tree | bd40604eb4b00a1a0e63edfe28cb0ebe33f23029 /quickstep/src/com/android/launcher3 | |
parent | ded14cc2110e39408f74abac8a83e0a0f16608d2 (diff) | |
parent | 7e8ae158f4a73ee580c468d71ca9a1df6f90a8cd (diff) | |
download | Launcher3-17dabff1dd38e8f0e08736176a8a587e039e610a.tar.gz |
Merge "Merge Android 14 QPR2 to AOSP main" into main
Diffstat (limited to 'quickstep/src/com/android/launcher3')
89 files changed, 3181 insertions, 1418 deletions
diff --git a/quickstep/src/com/android/launcher3/HomeTransitionController.java b/quickstep/src/com/android/launcher3/HomeTransitionController.java new file mode 100644 index 0000000000..b264115bf3 --- /dev/null +++ b/quickstep/src/com/android/launcher3/HomeTransitionController.java @@ -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; + } +} diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 741249d53e..4898761736 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -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(); - - // 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; - } + 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; } 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; } + } + + /** + * 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; + } - public int getBackgroundColor() { - return mBackgroundColor == Color.TRANSPARENT - ? mLauncher.getScrimView().getBackgroundColor() - : mBackgroundColor; + 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. diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java new file mode 100644 index 0000000000..43716abd97 --- /dev/null +++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java @@ -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)); + }); + } +} diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java index e8374b813c..037f7a8fea 100644 --- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java +++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java @@ -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) { diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java index f82474a8e8..1440498e15 100644 --- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java +++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java @@ -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(); } diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt new file mode 100644 index 0000000000..10733fbbea --- /dev/null +++ b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt @@ -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" + } +} diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index a63f9e8b29..baea418160 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -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; } diff --git a/quickstep/src/com/android/launcher3/model/PredictionHelper.java b/quickstep/src/com/android/launcher3/model/PredictionHelper.java index 738dd83cbc..dbd99e1eab 100644 --- a/quickstep/src/com/android/launcher3/model/PredictionHelper.java +++ b/quickstep/src/com/android/launcher3/model/PredictionHelper.java @@ -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; } diff --git a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java index e504141024..2fcbe4e0fe 100644 --- a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java +++ b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java @@ -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() diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java index 32361a862a..667f78498e 100644 --- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java +++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java @@ -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 diff --git a/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java b/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java index b982688ce7..8b71f01396 100644 --- a/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java +++ b/quickstep/src/com/android/launcher3/secondarydisplay/SecondaryDisplayPredictionsImpl.java @@ -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 diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java index ecf483caa4..a7e8118851 100644 --- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java +++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java @@ -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 @@ -103,18 +107,14 @@ public class DesktopVisibilityController { } /** - * 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(); diff --git a/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java b/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java index 331184a081..c201236586 100644 --- a/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java @@ -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(); diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopNavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/DesktopNavbarButtonsViewController.java index 885afffcbb..0a9dfff568 100644 --- a/quickstep/src/com/android/launcher3/taskbar/DesktopNavbarButtonsViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/DesktopNavbarButtonsViewController.java @@ -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) { } } diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java index 072fc3026e..f58fd4547d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java @@ -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(); diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java index 3e1a6ae9cb..a9d50b9332 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java @@ -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 diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java index 4e9e3019a9..42c423ce83 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java @@ -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) { @@ -439,6 +439,13 @@ public class KeyboardQuickSwitchView extends ConstraintLayout { } @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 && mViewCallbacks.onKeyUp(keyCode, event, mIsRtl, mDisplayingRecentTasks)) @@ -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() diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java index a293f748b9..b29ce6be14 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java @@ -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(); } diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 4e834ec3ef..159a6eff07 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -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 diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java index fcd8c806e4..bed4c376ae 100644 --- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java @@ -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; } diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java index c4255bf70f..da1f766420 100644 --- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java @@ -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 { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index b15dda2de1..4290948ad2 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -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); - // Inflate views. 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. 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) { + 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()); + }; mDeviceProfile = originDeviceProfile.toBuilder(this) - .withDimensionsOverride(deviceProfile -> { - // Taskbar should match the number of icons of hotseat - deviceProfile.numShownHotseatIcons = originDeviceProfile.numShownHotseatIcons; - // Same QSB width to have a smooth animation - deviceProfile.hotseatQsbWidth = originDeviceProfile.hotseatQsbWidth; - - // Update icon size - deviceProfile.iconSizePx = deviceProfile.taskbarIconSize; - deviceProfile.updateIconSize(1f, getResources()); - }).build(); + .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. */ @@ -1071,18 +1210,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext { } /** - * Called when we detect a long press in the nav region before passing the gesture slop. - * - * @return Whether taskbar handled the long press, and thus should cancel the gesture. - */ - public boolean onLongPressToUnstashTaskbar() { - 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); + } } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt index d237c1f997..d6016f1304 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt @@ -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) + 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(), 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)) - ) + 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) + } - lastDrawnTransientRect.set( - transientBackgroundBounds.left + halfWidthDelta, - bottom - newBackgroundHeight, - transientBackgroundBounds.right - halfWidthDelta, - bottom - ) - val horizontalInset = fullWidth * widthInsetPercentage - lastDrawnTransientRect.inset(horizontalInset, 0f) + // 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 - canvas.drawRoundRect(lastDrawnTransientRect, radius, radius, paint) + 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) } - canvas.restore() + + // 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) } /** diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java index d82f50116d..f9ddc3db2f 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java @@ -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[] { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupController.kt deleted file mode 100644 index 83a33435a8..0000000000 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupController.kt +++ /dev/null @@ -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:") - } -} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt index e215bc9d46..3f9b66a717 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt @@ -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) + } + } } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java index 3c7196a36f..6ddf9e940a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java @@ -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 diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java index e521154497..a24cf4ba58 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java @@ -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); } @@ -175,6 +188,15 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> { } /** + * 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. */ diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java index 867b062be1..73e32abb2c 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java @@ -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); } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt index de4175ddc1..6d1b55870f 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt @@ -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) } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarForceVisibleImmersiveController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarForceVisibleImmersiveController.java index ffaee455d9..333c07b8fa 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarForceVisibleImmersiveController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarForceVisibleImmersiveController.java @@ -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); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java index c3ec1e5ad0..044319796e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java @@ -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); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt index 5a4534e651..1a34b7a267 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt @@ -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) { @@ -154,6 +163,26 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas } /** + * 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. * Reason being WM does not allow types and number of insets changing for a given window once it @@ -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()) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java index 90f7beaf9c..057b71b74f 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java @@ -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"); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java index b115ca8032..bbac11625d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java @@ -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"); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java index 03df0365bb..3f72e5d9d3 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java @@ -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) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt new file mode 100644 index 0000000000..6cb28eee36 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt @@ -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 + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java index 512b77a92a..a667dcaa6d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java @@ -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) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimView.java index cdc6d59bf1..e40757a490 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimView.java @@ -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. diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java index 1c250bf377..712374db67 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java @@ -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); @@ -62,35 +66,60 @@ public class TaskbarScrimViewController implements TaskbarControllers.LoggableTa } /** + * 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(); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java index 66ca7d927f..d09f74c65a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java @@ -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; } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java index e8c8fc49fc..bfbecf3f77 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java @@ -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, diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java index a920dfaf6a..9aaa80f74c 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java @@ -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. @@ -165,21 +159,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. */ private static final boolean DEFAULT_STASHED_PREF = false; @@ -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()); } @@ -350,28 +338,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba } /** - * 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"); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java index 6fad655687..df2a43b170 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java @@ -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. @@ -323,6 +331,14 @@ public class TaskbarUIController { } /** + * 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. */ public void refreshResumedState() {} @@ -336,4 +352,7 @@ public class TaskbarUIController { .stream() .map(mControllers.taskbarPopupController::createSplitShortcutFactory); } + + /** Adjusts the hotseat for the bubble bar. */ + public void adjustHotseatForBubbleBar(boolean isBubbleBarVisible) {} } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index fa5a1ae6fa..2ab00665ed 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -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; diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index 4614e8ddc7..c0cbd45cc1 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -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). */ @@ -263,6 +321,80 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar } /** + * 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. */ public void setTranslationYForSwipe(float transY) { @@ -282,10 +414,41 @@ 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. */ protected void updateIconsBackground() { @@ -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. */ @@ -701,46 +904,18 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar } /** - * 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. + * Notifies launcher to update icon alignment. */ - public boolean onTouchEvent(MotionEvent motionEvent) { - final float x = motionEvent.getRawX(); - final float y = motionEvent.getRawY(); - switch (motionEvent.getAction()) { - case MotionEvent.ACTION_DOWN: - mDownX = x; - mDownY = y; - mControllers.taskbarStashController.startStashHint(/* animateForward = */ true); - mCanceledStashHint = false; - break; - case MotionEvent.ACTION_MOVE: - if (!mCanceledStashHint - && squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop) { - mControllers.taskbarStashController.startStashHint( - /* animateForward= */ false); - mCanceledStashHint = true; - return true; - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - if (!mCanceledStashHint) { - mControllers.taskbarStashController.startStashHint( - /* animateForward= */ false); - } - break; - } - - return false; + public void notifyIconLayoutBoundsChanged() { + mControllers.uiController.onIconLayoutBoundsChanged(); } /** - * Notifies launcher to update icon alignment. + * Notifies the taskbar scrim when the visibility of taskbar changes. */ - public void notifyIconLayoutBoundsChanged() { - mControllers.uiController.onIconLayoutBoundsChanged(); + public void notifyVisibilityChanged() { + mControllers.taskbarScrimViewController.onTaskbarVisibilityChanged( + mTaskbarView.getVisibility()); } } } diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java index d786d94149..07d86e4e78 100644 --- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java +++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java @@ -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; } diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java index 001c3bcfc0..964d329066 100644 --- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java +++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java @@ -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); } diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java index 85633e9037..b1c515177b 100644 --- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java @@ -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); } } } diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt index 8a2041fc34..3d15fbdebf 100644 --- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarSearchSessionController.kt @@ -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 diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java index 24db380553..3fb7247d58 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java @@ -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); diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java index 20b8e3b708..065dd58f16 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java @@ -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. diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissViewExt.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissViewExt.kt index 4b235a97d2..6c3f0d8796 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissViewExt.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissViewExt.kt @@ -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, diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java index a5ea5a9955..09021edb91 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java @@ -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); + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java index fbab59531c..c998d9704e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java @@ -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; + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java index 12cb8c5353..6549ad6846 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java @@ -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. */ diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt index b6820817c8..b516d6febe 100644 --- a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt +++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt @@ -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 + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt index 4a53c0c3b1..3f51958402 100644 --- a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt +++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt @@ -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() + } } } diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt index 931f692fa2..6b05e9a891 100644 --- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt +++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt @@ -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) } } diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt index 8525c6c90c..5a7bc4969c 100644 --- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt +++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt @@ -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() } } diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt index 2acd5d4eff..382e29832d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt +++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt @@ -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 } } diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt index c76311582d..e1ffa4dabf 100644 --- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt +++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt @@ -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() + } } } diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt index f0fe58197b..0368b1dea6 100644 --- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt +++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt @@ -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() + } + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt index a24002c44d..abdd32c712 100644 --- a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt +++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt @@ -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() + } } } diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt index 8332b7dd74..f5a4c64fd5 100644 --- a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt +++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt @@ -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() + } + } } } diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java index d4e2be9b49..adbec65ad3 100644 --- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java +++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java @@ -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 diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java index ff00560956..432d272761 100644 --- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java +++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayDragLayer.java @@ -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 diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java index 475f465d0e..4a265592c5 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java +++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java @@ -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; + } + } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java index d78ca88249..e2f4f322a7 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java @@ -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)); diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java index 2064fe22f6..110ca167aa 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java +++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java @@ -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); diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java index f5ba8f9cc5..8092582baf 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java @@ -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); diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 5a46b8d8e9..9438e00b43 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -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 @@ -332,11 +352,6 @@ public class QuickstepLauncher extends Launcher { } @Override - protected QuickstepOnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) { - return new QuickstepOnboardingPrefs(this, sharedPrefs); - } - - @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); onStateOrResumeChanging(false /* inTransition */); @@ -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 @@ -739,15 +784,6 @@ public class QuickstepLauncher extends Launcher { } @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); onTaskbarInAppDisplayProgressUpdate(progress, ALL_APPS_PAGE_PROGRESS_INDEX); @@ -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); @@ -1156,11 +1196,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; } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java index f7bef031c7..f66bc602a8 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java @@ -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); diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java index 481e20007e..630ef39627 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java +++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java @@ -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(); diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java index a76eb437de..c1a85fa71d 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java +++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java @@ -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(); + + // 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)); - mPreferenceScreen = getPreferenceManager().createPreferenceScreen(getContext()); - setPreferenceScreen(mPreferenceScreen); + 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) { diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java index 035beb4df1..915f4aeaf7 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java +++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java @@ -29,6 +29,11 @@ class DeviceFlag extends DebugFlag { } @Override + boolean currentValueModified() { + return super.currentValueModified() || mDefaultValueInCode != get(); + } + + @Override public String toString() { return super.toString() + ", mDefaultValueInCode=" + mDefaultValueInCode; } diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java index 87c836bbac..ec0566ac5c 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java +++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java @@ -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(); - } - - 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); - } + mFlagsApplyButton.setVisibility(anyChanged() ? View.VISIBLE : View.INVISIBLE); } - 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(); diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java index 6279f634d6..48d313e2d3 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java +++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java @@ -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); } } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/IntDeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/IntDeviceFlag.java new file mode 100644 index 0000000000..4f3b0ae137 --- /dev/null +++ b/quickstep/src/com/android/launcher3/uioverrides/flags/IntDeviceFlag.java @@ -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; + } +} diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java index ed0a0d5172..3767cce67f 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java @@ -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 diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java index e5787209d2..d11a08bff5 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java @@ -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; diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java index b9221eef9d..1d55da325e 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java @@ -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); diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java index 739246992c..ba44d6a795 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java @@ -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; diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java index b266bcd246..82a9c058df 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java @@ -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); diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java index 6f421eb14a..968faf07c7 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -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; } diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java index e30fe667ff..2c937b008e 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java @@ -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; diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java index 9a35bb2ae7..ff142fe2ce 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java @@ -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; } diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java index 26ab3d6c52..cda785504b 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java @@ -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; diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java index 3d94857848..19bfe069c8 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java @@ -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(); |