Merge Android 24Q1 Release (ab/11220357)
Bug: 319669529 Merged-In: I0708bf3c060ba84089722d0bd9480a4f4bd2b8e2 Change-Id: I31bb7f6aa8f71244f6e44903927f67e9d3a85642
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.launcher3;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.wm.shell.transition.IHomeTransitionListener;
|
||||
|
||||
/**
|
||||
* Controls launcher response to home activity visibility changing.
|
||||
*/
|
||||
public class HomeTransitionController {
|
||||
|
||||
private final QuickstepLauncher mLauncher;
|
||||
@Nullable private IHomeTransitionListener mHomeTransitionListener;
|
||||
|
||||
public HomeTransitionController(QuickstepLauncher launcher) {
|
||||
mLauncher = launcher;
|
||||
}
|
||||
|
||||
public void registerHomeTransitionListener() {
|
||||
mHomeTransitionListener = new IHomeTransitionListener.Stub() {
|
||||
@Override
|
||||
public void onHomeVisibilityChanged(boolean isVisible) {
|
||||
MAIN_EXECUTOR.execute(() -> {
|
||||
if (mLauncher.getTaskbarUIController() != null) {
|
||||
mLauncher.getTaskbarUIController().onLauncherVisibilityChanged(isVisible);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).setHomeTransitionListener(mHomeTransitionListener);
|
||||
}
|
||||
|
||||
public void unregisterHomeTransitionListener() {
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).setHomeTransitionListener(null);
|
||||
mHomeTransitionListener = null;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
|
||||
import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
|
||||
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
|
||||
import static android.view.RemoteAnimationTarget.MODE_OPENING;
|
||||
import static android.view.Surface.ROTATION_0;
|
||||
import static android.view.Surface.ROTATION_180;
|
||||
import static android.view.WindowManager.TRANSIT_CLOSE;
|
||||
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
|
||||
import static android.view.WindowManager.TRANSIT_OPEN;
|
||||
@@ -53,7 +55,9 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_SCRIM_FOR_APP_LAU
|
||||
import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
|
||||
import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
|
||||
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
|
||||
import static com.android.launcher3.testing.shared.TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE;
|
||||
import static com.android.launcher3.util.DisplayController.isTransientTaskbar;
|
||||
import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR;
|
||||
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
|
||||
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
|
||||
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
|
||||
@@ -74,8 +78,8 @@ import android.app.ActivityOptions;
|
||||
import android.app.WindowConfiguration;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.database.ContentObserver;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Point;
|
||||
@@ -90,6 +94,7 @@ import android.os.Looper;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.provider.Settings.Global;
|
||||
import android.util.Pair;
|
||||
import android.util.Size;
|
||||
import android.view.CrossWindowBlurListeners;
|
||||
@@ -116,6 +121,7 @@ import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
|
||||
import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory;
|
||||
import com.android.launcher3.anim.AnimationSuccessListener;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.compat.AccessibilityManagerCompat;
|
||||
import com.android.launcher3.dragndrop.DragLayer;
|
||||
import com.android.launcher3.icons.FastBitmapDrawable;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
@@ -160,6 +166,7 @@ import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
|
||||
import com.android.wm.shell.startingsurface.IStartingWindowListener;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@@ -181,9 +188,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
*/
|
||||
public static final int STATUS_BAR_TRANSITION_PRE_DELAY = 96;
|
||||
|
||||
private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
|
||||
"android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
|
||||
|
||||
public static final long APP_LAUNCH_DURATION = 500;
|
||||
|
||||
private static final long APP_LAUNCH_ALPHA_DURATION = 50;
|
||||
@@ -226,7 +230,18 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
private final float mClosingWindowTransY;
|
||||
private final float mMaxShadowRadius;
|
||||
|
||||
private final StartingWindowListener mStartingWindowListener = new StartingWindowListener();
|
||||
private final StartingWindowListener mStartingWindowListener =
|
||||
new StartingWindowListener(this);
|
||||
private ContentObserver mAnimationRemovalObserver = new ContentObserver(
|
||||
ORDERED_BG_EXECUTOR.getHandler()) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
mAreAnimationsEnabled = Global.getFloat(mLauncher.getContentResolver(),
|
||||
Global.ANIMATOR_DURATION_SCALE, 1f) > 0
|
||||
|| Global.getFloat(mLauncher.getContentResolver(),
|
||||
Global.TRANSITION_ANIMATION_SCALE, 1f) > 0;
|
||||
}
|
||||
};
|
||||
|
||||
private DeviceProfile mDeviceProfile;
|
||||
|
||||
@@ -238,6 +253,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
private RemoteAnimationFactory mWallpaperOpenTransitionRunner;
|
||||
private RemoteTransition mLauncherOpenTransition;
|
||||
|
||||
private final RemoteAnimationCoordinateTransfer mCoordinateTransfer;
|
||||
|
||||
private LauncherBackAnimationController mBackAnimationController;
|
||||
private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
@@ -254,6 +271,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
// Pairs of window starting type and starting window background color for starting tasks
|
||||
// Will never be larger than MAX_NUM_TASKS
|
||||
private LinkedHashMap<Integer, Pair<Integer, Integer>> mTaskStartParams;
|
||||
private boolean mAreAnimationsEnabled = true;
|
||||
|
||||
private final Interpolator mOpeningXInterpolator;
|
||||
private final Interpolator mOpeningInterpolator;
|
||||
@@ -264,6 +282,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
mDeviceProfile = mLauncher.getDeviceProfile();
|
||||
mBackAnimationController = new LauncherBackAnimationController(mLauncher, this);
|
||||
checkAndMonitorIfAnimationsAreEnabled();
|
||||
|
||||
Resources res = mLauncher.getResources();
|
||||
mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
|
||||
@@ -271,15 +290,14 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
|
||||
mLauncher.addOnDeviceProfileChangeListener(this);
|
||||
|
||||
if (supportsSSplashScreen()) {
|
||||
mTaskStartParams = new LinkedHashMap<Integer, Pair<Integer, Integer>>(MAX_NUM_TASKS) {
|
||||
if (ENABLE_SHELL_STARTING_SURFACE) {
|
||||
mTaskStartParams = new LinkedHashMap<>(MAX_NUM_TASKS) {
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Entry<Integer, Pair<Integer, Integer>> entry) {
|
||||
return size() > MAX_NUM_TASKS;
|
||||
}
|
||||
};
|
||||
|
||||
mStartingWindowListener.setTransitionManager(this);
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(
|
||||
mStartingWindowListener);
|
||||
}
|
||||
@@ -287,6 +305,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
mOpeningXInterpolator = AnimationUtils.loadInterpolator(context, R.interpolator.app_open_x);
|
||||
mOpeningInterpolator = AnimationUtils.loadInterpolator(context,
|
||||
R.interpolator.emphasized_interpolator);
|
||||
mCoordinateTransfer = new RemoteAnimationCoordinateTransfer(mLauncher);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -311,8 +330,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
mAppLaunchRunner = new AppLaunchAnimationRunner(v, onEndCallback);
|
||||
ItemInfo tag = (ItemInfo) v.getTag();
|
||||
if (tag != null && tag.shouldUseBackgroundAnimation()) {
|
||||
ContainerAnimationRunner containerAnimationRunner =
|
||||
ContainerAnimationRunner.from(v, mStartingWindowListener, onEndCallback);
|
||||
ContainerAnimationRunner containerAnimationRunner = ContainerAnimationRunner.from(
|
||||
v, mLauncher, mStartingWindowListener, onEndCallback);
|
||||
if (containerAnimationRunner != null) {
|
||||
mAppLaunchRunner = containerAnimationRunner;
|
||||
}
|
||||
@@ -474,9 +493,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
/**
|
||||
* Content is everything on screen except the background and the floating view (if any).
|
||||
*
|
||||
* @param isAppOpening True when this is called when an app is opening.
|
||||
* False when this is called when an app is closing.
|
||||
* @param startDelay Start delay duration.
|
||||
* @param isAppOpening True when this is called when an app is opening.
|
||||
* False when this is called when an app is closing.
|
||||
* @param startDelay Start delay duration.
|
||||
* @param skipAllAppsScale True if we want to avoid scaling All Apps
|
||||
*/
|
||||
private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening,
|
||||
@@ -660,7 +679,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
mDragLayer.getLocationOnScreen(dragLayerBounds);
|
||||
|
||||
final boolean hasSplashScreen;
|
||||
if (supportsSSplashScreen()) {
|
||||
if (ENABLE_SHELL_STARTING_SURFACE) {
|
||||
int taskId = openingTargets.getFirstAppTargetTaskId();
|
||||
Pair<Integer, Integer> defaultParams = Pair.create(STARTING_WINDOW_TYPE_NONE, 0);
|
||||
Pair<Integer, Integer> taskParams =
|
||||
@@ -905,7 +924,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
|
||||
RemoteAnimationTarget openingTarget = openingTargets.getFirstAppTarget();
|
||||
int fallbackBackgroundColor = 0;
|
||||
if (openingTarget != null && supportsSSplashScreen()) {
|
||||
if (openingTarget != null && ENABLE_SHELL_STARTING_SURFACE) {
|
||||
fallbackBackgroundColor = mTaskStartParams.containsKey(openingTarget.taskId)
|
||||
? mTaskStartParams.get(openingTarget.taskId).second : 0;
|
||||
mTaskStartParams.remove(openingTarget.taskId);
|
||||
@@ -1043,7 +1062,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
LaunchDepthController depthController = new LaunchDepthController(mLauncher);
|
||||
ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController.stateDepth,
|
||||
MULTI_PROPERTY_VALUE, BACKGROUND_APP.getDepth(mLauncher))
|
||||
.setDuration(APP_LAUNCH_DURATION);
|
||||
.setDuration(APP_LAUNCH_DURATION);
|
||||
|
||||
if (allowBlurringLauncher) {
|
||||
// Create a temporary effect layer, that lives on top of launcher, so we can apply
|
||||
@@ -1081,11 +1100,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
if (SEPARATE_RECENTS_ACTIVITY.get()) {
|
||||
return;
|
||||
}
|
||||
if (hasControlRemoteAppTransitionPermission()) {
|
||||
RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
|
||||
addRemoteAnimations(definition);
|
||||
mLauncher.registerRemoteAnimations(definition);
|
||||
}
|
||||
RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
|
||||
addRemoteAnimations(definition);
|
||||
mLauncher.registerRemoteAnimations(definition);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1123,28 +1140,27 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
if (SEPARATE_RECENTS_ACTIVITY.get()) {
|
||||
return;
|
||||
}
|
||||
if (hasControlRemoteAppTransitionPermission()) {
|
||||
mWallpaperOpenTransitionRunner = createWallpaperOpenRunner(false /* fromUnlock */);
|
||||
mLauncherOpenTransition = new RemoteTransition(
|
||||
new LauncherAnimationRunner(mHandler, mWallpaperOpenTransitionRunner,
|
||||
false /* startAtFrontOfQueue */).toRemoteTransition(),
|
||||
mLauncher.getIApplicationThread(), "QuickstepLaunchHome");
|
||||
|
||||
TransitionFilter homeCheck = new TransitionFilter();
|
||||
// No need to handle the transition that also dismisses keyguard.
|
||||
homeCheck.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
|
||||
homeCheck.mRequirements =
|
||||
new TransitionFilter.Requirement[]{new TransitionFilter.Requirement(),
|
||||
new TransitionFilter.Requirement()};
|
||||
homeCheck.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME;
|
||||
homeCheck.mRequirements[0].mTopActivity = mLauncher.getComponentName();
|
||||
homeCheck.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
|
||||
homeCheck.mRequirements[0].mOrder = CONTAINER_ORDER_TOP;
|
||||
homeCheck.mRequirements[1].mActivityType = ACTIVITY_TYPE_STANDARD;
|
||||
homeCheck.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
|
||||
SystemUiProxy.INSTANCE.get(mLauncher)
|
||||
.registerRemoteTransition(mLauncherOpenTransition, homeCheck);
|
||||
}
|
||||
mWallpaperOpenTransitionRunner = createWallpaperOpenRunner(false /* fromUnlock */);
|
||||
mLauncherOpenTransition = new RemoteTransition(
|
||||
new LauncherAnimationRunner(mHandler, mWallpaperOpenTransitionRunner,
|
||||
false /* startAtFrontOfQueue */).toRemoteTransition(),
|
||||
mLauncher.getIApplicationThread(), "QuickstepLaunchHome");
|
||||
|
||||
TransitionFilter homeCheck = new TransitionFilter();
|
||||
// No need to handle the transition that also dismisses keyguard.
|
||||
homeCheck.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
|
||||
homeCheck.mRequirements =
|
||||
new TransitionFilter.Requirement[]{new TransitionFilter.Requirement(),
|
||||
new TransitionFilter.Requirement()};
|
||||
homeCheck.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME;
|
||||
homeCheck.mRequirements[0].mTopActivity = mLauncher.getComponentName();
|
||||
homeCheck.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
|
||||
homeCheck.mRequirements[0].mOrder = CONTAINER_ORDER_TOP;
|
||||
homeCheck.mRequirements[1].mActivityType = ACTIVITY_TYPE_STANDARD;
|
||||
homeCheck.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
|
||||
SystemUiProxy.INSTANCE.get(mLauncher)
|
||||
.registerRemoteTransition(mLauncherOpenTransition, homeCheck);
|
||||
if (mBackAnimationController != null) {
|
||||
mBackAnimationController.registerBackCallbacks(mHandler);
|
||||
}
|
||||
@@ -1153,23 +1169,22 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
public void onActivityDestroyed() {
|
||||
unregisterRemoteAnimations();
|
||||
unregisterRemoteTransitions();
|
||||
mStartingWindowListener.setTransitionManager(null);
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null);
|
||||
ORDERED_BG_EXECUTOR.execute(() -> mLauncher.getContentResolver()
|
||||
.unregisterContentObserver(mAnimationRemovalObserver));
|
||||
}
|
||||
|
||||
private void unregisterRemoteAnimations() {
|
||||
if (SEPARATE_RECENTS_ACTIVITY.get()) {
|
||||
return;
|
||||
}
|
||||
if (hasControlRemoteAppTransitionPermission()) {
|
||||
mLauncher.unregisterRemoteAnimations();
|
||||
mLauncher.unregisterRemoteAnimations();
|
||||
|
||||
// Also clear strong references to the runners registered with the remote animation
|
||||
// definition so we don't have to wait for the system gc
|
||||
mWallpaperOpenRunner = null;
|
||||
mAppLaunchRunner = null;
|
||||
mKeyguardGoingAwayRunner = null;
|
||||
}
|
||||
// Also clear strong references to the runners registered with the remote animation
|
||||
// definition so we don't have to wait for the system gc
|
||||
mWallpaperOpenRunner = null;
|
||||
mAppLaunchRunner = null;
|
||||
mKeyguardGoingAwayRunner = null;
|
||||
}
|
||||
|
||||
protected void unregisterRemoteTransitions() {
|
||||
@@ -1179,19 +1194,28 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
if (SEPARATE_RECENTS_ACTIVITY.get()) {
|
||||
return;
|
||||
}
|
||||
if (hasControlRemoteAppTransitionPermission()) {
|
||||
if (mLauncherOpenTransition == null) return;
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).unregisterRemoteTransition(
|
||||
mLauncherOpenTransition);
|
||||
mLauncherOpenTransition = null;
|
||||
mWallpaperOpenTransitionRunner = null;
|
||||
}
|
||||
if (mLauncherOpenTransition == null) return;
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).unregisterRemoteTransition(
|
||||
mLauncherOpenTransition);
|
||||
mLauncherOpenTransition = null;
|
||||
mWallpaperOpenTransitionRunner = null;
|
||||
if (mBackAnimationController != null) {
|
||||
mBackAnimationController.unregisterBackCallbacks();
|
||||
mBackAnimationController = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAndMonitorIfAnimationsAreEnabled() {
|
||||
ORDERED_BG_EXECUTOR.execute(() -> {
|
||||
mAnimationRemovalObserver.onChange(true);
|
||||
mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
|
||||
Global.ANIMATOR_DURATION_SCALE), false, mAnimationRemovalObserver);
|
||||
mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
|
||||
Global.TRANSITION_ANIMATION_SCALE), false, mAnimationRemovalObserver);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private boolean launcherIsATargetWithMode(RemoteAnimationTarget[] targets, int mode) {
|
||||
for (RemoteAnimationTarget target : targets) {
|
||||
if (target.mode == mode && target.taskInfo != null
|
||||
@@ -1287,7 +1311,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
return null;
|
||||
}
|
||||
|
||||
final ComponentName[] taskInfoActivities = new ComponentName[] {
|
||||
final ComponentName[] taskInfoActivities = new ComponentName[]{
|
||||
runningTaskTarget.taskInfo.baseActivity,
|
||||
runningTaskTarget.taskInfo.origActivity,
|
||||
runningTaskTarget.taskInfo.realActivity,
|
||||
@@ -1333,7 +1357,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
.getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx);
|
||||
float secondaryDimension = orientationHandler
|
||||
.getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx);
|
||||
final float targetX = primaryDimension / 2f;
|
||||
final float targetX = primaryDimension / 2f;
|
||||
final float targetY = secondaryDimension - dp.hotseatBarSizePx;
|
||||
return new RectF(targetX - halfIconSize, targetY - halfIconSize,
|
||||
targetX + halfIconSize, targetY + halfIconSize);
|
||||
@@ -1344,7 +1368,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
*/
|
||||
protected RectFSpringAnim getClosingWindowAnimators(AnimatorSet animation,
|
||||
RemoteAnimationTarget[] targets, View launcherView, PointF velocityPxPerS,
|
||||
RectF closingWindowStartRect, float startWindowCornerRadius) {
|
||||
RectF closingWindowStartRectF, float startWindowCornerRadius) {
|
||||
FloatingIconView floatingIconView = null;
|
||||
FloatingWidgetView floatingWidget = null;
|
||||
RectF targetRect = new RectF();
|
||||
@@ -1370,7 +1394,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
(LauncherAppWidgetHostView) launcherView, targetRect, windowSize,
|
||||
mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher),
|
||||
isTransluscent, fallbackBackgroundColor);
|
||||
} else if (launcherView != null) {
|
||||
} else if (launcherView != null && mAreAnimationsEnabled) {
|
||||
floatingIconView = getFloatingIconView(mLauncher, launcherView, null,
|
||||
mLauncher.getTaskbarUIController() == null
|
||||
? null
|
||||
@@ -1384,13 +1408,16 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
|
||||
boolean useTaskbarHotseatParams = mDeviceProfile.isTaskbarPresent && isInHotseat;
|
||||
RectFSpringAnim anim = new RectFSpringAnim(useTaskbarHotseatParams
|
||||
? new TaskbarHotseatSpringConfig(mLauncher, closingWindowStartRect, targetRect)
|
||||
: new DefaultSpringConfig(mLauncher, mDeviceProfile, closingWindowStartRect,
|
||||
? new TaskbarHotseatSpringConfig(mLauncher, closingWindowStartRectF, targetRect)
|
||||
: new DefaultSpringConfig(mLauncher, mDeviceProfile, closingWindowStartRectF,
|
||||
targetRect));
|
||||
|
||||
// Hook up floating views to the closing window animators.
|
||||
final int rotationChange = getRotationChange(targets);
|
||||
Rect windowTargetBounds = getWindowTargetBounds(targets, rotationChange);
|
||||
// note the coordinate of closingWindowStartRect is based on launcher
|
||||
Rect closingWindowStartRect = new Rect();
|
||||
closingWindowStartRectF.round(closingWindowStartRect);
|
||||
Rect closingWindowOriginalRect =
|
||||
new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);
|
||||
if (floatingIconView != null) {
|
||||
anim.addAnimatorListener(floatingIconView);
|
||||
floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged);
|
||||
@@ -1402,7 +1429,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
final float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION;
|
||||
|
||||
RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
|
||||
windowTargetBounds, startWindowCornerRadius) {
|
||||
closingWindowStartRect, closingWindowOriginalRect, startWindowCornerRadius) {
|
||||
@Override
|
||||
public void onUpdate(RectF currentRectF, float progress) {
|
||||
finalFloatingIconView.update(1f, currentRectF, progress, windowAlphaThreshold,
|
||||
@@ -1420,7 +1447,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
final float floatingWidgetAlpha = isTransluscent ? 0 : 1;
|
||||
FloatingWidgetView finalFloatingWidget = floatingWidget;
|
||||
RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
|
||||
windowTargetBounds, startWindowCornerRadius) {
|
||||
closingWindowStartRect, closingWindowOriginalRect, startWindowCornerRadius) {
|
||||
@Override
|
||||
public void onUpdate(RectF currentRectF, float progress) {
|
||||
final float fallbackBackgroundAlpha =
|
||||
@@ -1438,7 +1465,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
// If no floating icon or widget is present, animate the to the default window
|
||||
// target rect.
|
||||
anim.addOnUpdateListener(new SpringAnimRunner(
|
||||
targets, targetRect, windowTargetBounds, startWindowCornerRadius));
|
||||
targets, targetRect, closingWindowStartRect, closingWindowOriginalRect,
|
||||
startWindowCornerRadius));
|
||||
}
|
||||
|
||||
// Use a fixed velocity to start the animation.
|
||||
@@ -1519,20 +1547,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
return closingAnimator;
|
||||
}
|
||||
|
||||
private boolean supportsSSplashScreen() {
|
||||
return hasControlRemoteAppTransitionPermission()
|
||||
&& Utilities.ATLEAST_S
|
||||
&& ENABLE_SHELL_STARTING_SURFACE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we have permission to control remote app transisions
|
||||
*/
|
||||
public boolean hasControlRemoteAppTransitionPermission() {
|
||||
return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
private void addCujInstrumentation(Animator anim, int cuj) {
|
||||
anim.addListener(new AnimationSuccessListener() {
|
||||
@Override
|
||||
@@ -1638,7 +1652,17 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
// is initialized.
|
||||
if (launcherIsForceInvisibleOrOpening) {
|
||||
addCujInstrumentation(anim, playFallBackAnimation
|
||||
? CUJ_APP_CLOSE_TO_HOME_FALLBACK : CUJ_APP_CLOSE_TO_HOME);
|
||||
? CUJ_APP_CLOSE_TO_HOME_FALLBACK : CUJ_APP_CLOSE_TO_HOME);
|
||||
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
AccessibilityManagerCompat.sendTestProtocolEventToTest(
|
||||
mLauncher, WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE);
|
||||
}
|
||||
});
|
||||
|
||||
// Only register the content animation for cancellation when state changes
|
||||
mLauncher.getStateManager().setCurrentAnimation(anim);
|
||||
|
||||
@@ -1694,8 +1718,18 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
|
||||
RectF windowTargetBounds =
|
||||
new RectF(getWindowTargetBounds(appTargets, getRotationChange(appTargets)));
|
||||
|
||||
final RectF resolveRectF = new RectF(windowTargetBounds);
|
||||
for (RemoteAnimationTarget t : appTargets) {
|
||||
if (t.mode == MODE_CLOSING) {
|
||||
transferRectToTargetCoordinate(
|
||||
t, windowTargetBounds, true, resolveRectF);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Pair<RectFSpringAnim, AnimatorSet> pair = createWallpaperOpenAnimations(
|
||||
appTargets, wallpaperTargets, mFromUnlock, windowTargetBounds,
|
||||
appTargets, wallpaperTargets, mFromUnlock, resolveRectF,
|
||||
QuickStepContract.getWindowCornerRadius(mLauncher),
|
||||
false /* fromPredictiveBack */);
|
||||
|
||||
@@ -1776,11 +1810,11 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ContainerAnimationRunner from(
|
||||
View v, StartingWindowListener startingWindowListener, RunnableList onEndCallback) {
|
||||
private static ContainerAnimationRunner from(View v, Launcher launcher,
|
||||
StartingWindowListener startingWindowListener, RunnableList onEndCallback) {
|
||||
View viewToUse = findLaunchableViewWithBackground(v);
|
||||
if (viewToUse == null) {
|
||||
viewToUse = v;
|
||||
return null;
|
||||
}
|
||||
|
||||
// The CUJ is logged by the click handler, so we don't log it inside the animation
|
||||
@@ -1802,8 +1836,13 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
}
|
||||
};
|
||||
|
||||
ActivityLaunchAnimator.Callback callback = task -> ColorUtils.setAlphaComponent(
|
||||
startingWindowListener.getBackgroundColor(), 255);
|
||||
ActivityLaunchAnimator.Callback callback = task -> {
|
||||
final int backgroundColor =
|
||||
startingWindowListener.mBackgroundColor == Color.TRANSPARENT
|
||||
? launcher.getScrimView().getBackgroundColor()
|
||||
: startingWindowListener.mBackgroundColor;
|
||||
return ColorUtils.setAlphaComponent(backgroundColor, 255);
|
||||
};
|
||||
|
||||
ActivityLaunchAnimator.Listener listener = new ActivityLaunchAnimator.Listener() {
|
||||
@Override
|
||||
@@ -1917,24 +1956,68 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
}
|
||||
}
|
||||
|
||||
private class StartingWindowListener extends IStartingWindowListener.Stub {
|
||||
private QuickstepTransitionManager mTransitionManager;
|
||||
private static class StartingWindowListener extends IStartingWindowListener.Stub {
|
||||
private final WeakReference<QuickstepTransitionManager> mTransitionManagerRef;
|
||||
private int mBackgroundColor;
|
||||
|
||||
public void setTransitionManager(QuickstepTransitionManager transitionManager) {
|
||||
mTransitionManager = transitionManager;
|
||||
private StartingWindowListener(QuickstepTransitionManager transitionManager) {
|
||||
mTransitionManagerRef = new WeakReference<>(transitionManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskLaunching(int taskId, int supportedType, int color) {
|
||||
mTransitionManager.mTaskStartParams.put(taskId, Pair.create(supportedType, color));
|
||||
QuickstepTransitionManager transitionManager = mTransitionManagerRef.get();
|
||||
if (transitionManager != null) {
|
||||
transitionManager.mTaskStartParams.put(taskId, Pair.create(supportedType, color));
|
||||
}
|
||||
mBackgroundColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
public int getBackgroundColor() {
|
||||
return mBackgroundColor == Color.TRANSPARENT
|
||||
? mLauncher.getScrimView().getBackgroundColor()
|
||||
: mBackgroundColor;
|
||||
/**
|
||||
* Transfer the rectangle to another coordinate if needed.
|
||||
*
|
||||
* @param toLauncher which one is the anchor of this transfer, if true then transfer from
|
||||
* animation target to launcher, false transfer from launcher to animation
|
||||
* target.
|
||||
*/
|
||||
public void transferRectToTargetCoordinate(RemoteAnimationTarget target, RectF currentRect,
|
||||
boolean toLauncher, RectF resultRect) {
|
||||
mCoordinateTransfer.transferRectToTargetCoordinate(
|
||||
target, currentRect, toLauncher, resultRect);
|
||||
}
|
||||
|
||||
private static class RemoteAnimationCoordinateTransfer {
|
||||
private final QuickstepLauncher mLauncher;
|
||||
private final Rect mDisplayRect = new Rect();
|
||||
private final Rect mTmpResult = new Rect();
|
||||
|
||||
RemoteAnimationCoordinateTransfer(QuickstepLauncher launcher) {
|
||||
mLauncher = launcher;
|
||||
}
|
||||
|
||||
void transferRectToTargetCoordinate(RemoteAnimationTarget target, RectF currentRect,
|
||||
boolean toLauncher, RectF resultRect) {
|
||||
final int taskRotation = target.windowConfiguration.getRotation();
|
||||
final DeviceProfile profile = mLauncher.getDeviceProfile();
|
||||
|
||||
final int rotationDelta = toLauncher
|
||||
? android.util.RotationUtils.deltaRotation(taskRotation, profile.rotationHint)
|
||||
: android.util.RotationUtils.deltaRotation(profile.rotationHint, taskRotation);
|
||||
if (rotationDelta != ROTATION_0) {
|
||||
// Get original display size when task is on top but with different rotation
|
||||
if (rotationDelta % 2 != 0 && toLauncher && (profile.rotationHint == ROTATION_0
|
||||
|| profile.rotationHint == ROTATION_180)) {
|
||||
mDisplayRect.set(0, 0, profile.heightPx, profile.widthPx);
|
||||
} else {
|
||||
mDisplayRect.set(0, 0, profile.widthPx, profile.heightPx);
|
||||
}
|
||||
currentRect.round(mTmpResult);
|
||||
android.util.RotationUtils.rotateBounds(mTmpResult, mDisplayRect, rotationDelta);
|
||||
resultRect.set(mTmpResult);
|
||||
} else {
|
||||
resultRect.set(currentRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1949,17 +2032,49 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
private final float mStartRadius;
|
||||
private final float mEndRadius;
|
||||
private final SurfaceTransactionApplier mSurfaceApplier;
|
||||
private final Rect mWindowTargetBounds = new Rect();
|
||||
private final Rect mWindowStartBounds = new Rect();
|
||||
private final Rect mWindowOriginalBounds = new Rect();
|
||||
|
||||
private final Rect mTmpRect = new Rect();
|
||||
|
||||
/**
|
||||
* Constructor for SpringAnimRunner
|
||||
*
|
||||
* @param appTargets the list of opening/closing apps
|
||||
* @param targetRect target rectangle
|
||||
* @param closingWindowStartRect start position of the window when the spring animation
|
||||
* is started. In the predictive back to home case this
|
||||
* will be smaller than closingWindowOriginalRect because
|
||||
* the window is already scaled by the user gesture
|
||||
* @param closingWindowOriginalRect Original unscaled window rect
|
||||
* @param startWindowCornerRadius corner radius of window at the start position
|
||||
*/
|
||||
SpringAnimRunner(RemoteAnimationTarget[] appTargets, RectF targetRect,
|
||||
Rect windowTargetBounds, float startWindowCornerRadius) {
|
||||
Rect closingWindowStartRect, Rect closingWindowOriginalRect,
|
||||
float startWindowCornerRadius) {
|
||||
mAppTargets = appTargets;
|
||||
mStartRadius = startWindowCornerRadius;
|
||||
mEndRadius = Math.max(1, targetRect.width()) / 2f;
|
||||
mSurfaceApplier = new SurfaceTransactionApplier(mDragLayer);
|
||||
mWindowTargetBounds.set(windowTargetBounds);
|
||||
mWindowStartBounds.set(closingWindowStartRect);
|
||||
mWindowOriginalBounds.set(closingWindowOriginalRect);
|
||||
|
||||
// transfer the coordinate based on animation target.
|
||||
if (mAppTargets != null) {
|
||||
for (RemoteAnimationTarget t : mAppTargets) {
|
||||
if (t.mode == MODE_CLOSING) {
|
||||
final RectF transferRect = new RectF(mWindowStartBounds);
|
||||
final RectF result = new RectF();
|
||||
transferRectToTargetCoordinate(t, transferRect, false, result);
|
||||
result.round(mWindowStartBounds);
|
||||
|
||||
transferRect.set(closingWindowOriginalRect);
|
||||
transferRectToTargetCoordinate(t, transferRect, false, result);
|
||||
result.round(mWindowOriginalBounds);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float getCornerRadius(float progress) {
|
||||
@@ -1980,26 +2095,28 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
}
|
||||
|
||||
if (target.mode == MODE_CLOSING) {
|
||||
transferRectToTargetCoordinate(target, currentRectF, false, currentRectF);
|
||||
currentRectF.round(mCurrentRect);
|
||||
|
||||
// Scale the target window to match the currentRectF.
|
||||
final float scale;
|
||||
|
||||
// We need to infer the crop (we crop the window to match the currentRectF).
|
||||
if (mWindowTargetBounds.height() > mWindowTargetBounds.width()) {
|
||||
scale = Math.min(1f, currentRectF.width() / mWindowTargetBounds.width());
|
||||
if (mWindowStartBounds.height() > mWindowStartBounds.width()) {
|
||||
scale = Math.min(1f, currentRectF.width() / mWindowOriginalBounds.width());
|
||||
|
||||
int unscaledHeight = (int) (mCurrentRect.height() * (1f / scale));
|
||||
int croppedHeight = mWindowTargetBounds.height() - unscaledHeight;
|
||||
mTmpRect.set(0, 0, mWindowTargetBounds.width(),
|
||||
mWindowTargetBounds.height() - croppedHeight);
|
||||
int croppedHeight = mWindowStartBounds.height() - unscaledHeight;
|
||||
mTmpRect.set(0, 0, mWindowOriginalBounds.width(),
|
||||
mWindowStartBounds.height() - croppedHeight);
|
||||
} else {
|
||||
scale = Math.min(1f, currentRectF.height() / mWindowTargetBounds.height());
|
||||
scale = Math.min(1f, currentRectF.height()
|
||||
/ mWindowOriginalBounds.height());
|
||||
|
||||
int unscaledWidth = (int) (mCurrentRect.width() * (1f / scale));
|
||||
int croppedWidth = mWindowTargetBounds.width() - unscaledWidth;
|
||||
mTmpRect.set(0, 0, mWindowTargetBounds.width() - croppedWidth,
|
||||
mWindowTargetBounds.height());
|
||||
int croppedWidth = mWindowStartBounds.width() - unscaledWidth;
|
||||
mTmpRect.set(0, 0, mWindowStartBounds.width() - croppedWidth,
|
||||
mWindowOriginalBounds.height());
|
||||
}
|
||||
|
||||
// Match size and position of currentRect.
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3;
|
||||
|
||||
import static android.view.WindowInsets.Type.navigationBars;
|
||||
import static android.view.WindowInsets.Type.statusBars;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowInsetsController;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.dragndrop.SimpleDragLayer;
|
||||
import com.android.launcher3.model.WidgetsModel;
|
||||
import com.android.launcher3.popup.PopupDataProvider;
|
||||
import com.android.launcher3.widget.BaseWidgetSheet;
|
||||
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
|
||||
import com.android.launcher3.widget.picker.WidgetsFullSheet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/** An Activity that can host Launcher's widget picker. */
|
||||
public class WidgetPickerActivity extends BaseActivity {
|
||||
private SimpleDragLayer<WidgetPickerActivity> mDragLayer;
|
||||
private WidgetsModel mModel;
|
||||
private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {});
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
|
||||
|
||||
LauncherAppState app = LauncherAppState.getInstance(this);
|
||||
InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
|
||||
|
||||
mDeviceProfile = idp.getDeviceProfile(this);
|
||||
mModel = new WidgetsModel();
|
||||
|
||||
setContentView(R.layout.widget_picker_activity);
|
||||
mDragLayer = findViewById(R.id.drag_layer);
|
||||
mDragLayer.recreateControllers();
|
||||
|
||||
WindowInsetsController wc = mDragLayer.getWindowInsetsController();
|
||||
wc.hide(navigationBars() + statusBars());
|
||||
|
||||
BaseWidgetSheet widgetSheet = WidgetsFullSheet.show(this, true);
|
||||
widgetSheet.disableNavBarScrim(true);
|
||||
widgetSheet.addOnCloseListener(this::finish);
|
||||
|
||||
refreshAndBindWidgets();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public PopupDataProvider getPopupDataProvider() {
|
||||
return mPopupDataProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleDragLayer<WidgetPickerActivity> getDragLayer() {
|
||||
return mDragLayer;
|
||||
}
|
||||
|
||||
private void refreshAndBindWidgets() {
|
||||
MODEL_EXECUTOR.execute(() -> {
|
||||
LauncherAppState app = LauncherAppState.getInstance(this);
|
||||
mModel.update(app, null);
|
||||
final ArrayList<WidgetsListBaseEntry> widgets =
|
||||
mModel.getWidgetsListForPicker(app.getContext());
|
||||
MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -35,9 +35,7 @@ import androidx.core.content.ContextCompat;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.allapps.FloatingHeaderRow;
|
||||
import com.android.launcher3.allapps.FloatingHeaderView;
|
||||
import com.android.launcher3.util.OnboardingPrefs;
|
||||
import com.android.launcher3.util.Themes;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
|
||||
/**
|
||||
* A view which shows a horizontal divider
|
||||
@@ -93,10 +91,7 @@ public class AppsDividerView extends View implements FloatingHeaderRow {
|
||||
? R.color.all_apps_label_text_dark
|
||||
: R.color.all_apps_label_text);
|
||||
|
||||
OnboardingPrefs<?> onboardingPrefs = ActivityContext.lookupContext(
|
||||
getContext()).getOnboardingPrefs();
|
||||
mShowAllAppsLabel = onboardingPrefs == null || !onboardingPrefs.hasReachedMaxCount(
|
||||
ALL_APPS_VISITED_COUNT);
|
||||
mShowAllAppsLabel = !ALL_APPS_VISITED_COUNT.hasReachedMax(context);
|
||||
}
|
||||
|
||||
public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) {
|
||||
|
||||
@@ -36,6 +36,7 @@ import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.allapps.FloatingHeaderRow;
|
||||
import com.android.launcher3.allapps.FloatingHeaderView;
|
||||
import com.android.launcher3.anim.AlphaUpdateListener;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.keyboard.FocusIndicatorHelper;
|
||||
import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
@@ -126,6 +127,10 @@ public class PredictionRowView<T extends Context & ActivityContext>
|
||||
int verticalPadding = getResources().getDimensionPixelSize(
|
||||
R.dimen.all_apps_predicted_icon_vertical_padding);
|
||||
int totalHeight = iconHeight + iconPadding + textHeight + verticalPadding * 2;
|
||||
if (FeatureFlags.enableTwolineAllapps()) {
|
||||
// Add extra textHeight to the existing total height.
|
||||
totalHeight += textHeight;
|
||||
}
|
||||
return getVisibility() == GONE ? 0 : totalHeight + getPaddingTop() + getPaddingBottom();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.launcher3.desktop
|
||||
|
||||
import android.app.IApplicationThread
|
||||
import android.os.IBinder
|
||||
import android.os.RemoteException
|
||||
import android.util.Log
|
||||
import android.view.SurfaceControl
|
||||
import android.window.IRemoteTransition
|
||||
import android.window.IRemoteTransitionFinishedCallback
|
||||
import android.window.RemoteTransition
|
||||
import android.window.TransitionInfo
|
||||
import com.android.launcher3.statehandlers.DepthController
|
||||
import com.android.launcher3.statemanager.StateManager
|
||||
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
|
||||
import com.android.quickstep.SystemUiProxy
|
||||
import com.android.quickstep.TaskViewUtils
|
||||
import com.android.quickstep.views.DesktopTaskView
|
||||
import java.util.function.Consumer
|
||||
|
||||
/** Manage recents related operations with desktop tasks */
|
||||
class DesktopRecentsTransitionController(
|
||||
private val stateManager: StateManager<*>,
|
||||
private val systemUiProxy: SystemUiProxy,
|
||||
private val appThread: IApplicationThread,
|
||||
private val depthController: DepthController?
|
||||
) {
|
||||
|
||||
/** Launch desktop tasks from recents view */
|
||||
fun launchDesktopFromRecents(
|
||||
desktopTaskView: DesktopTaskView,
|
||||
callback: Consumer<Boolean>? = null
|
||||
) {
|
||||
val animRunner =
|
||||
RemoteDesktopLaunchTransitionRunner(
|
||||
desktopTaskView,
|
||||
stateManager,
|
||||
depthController,
|
||||
callback
|
||||
)
|
||||
val transition = RemoteTransition(animRunner, appThread, "RecentsToDesktop")
|
||||
systemUiProxy.showDesktopApps(desktopTaskView.display.displayId, transition)
|
||||
}
|
||||
|
||||
private class RemoteDesktopLaunchTransitionRunner(
|
||||
private val desktopTaskView: DesktopTaskView,
|
||||
private val stateManager: StateManager<*>,
|
||||
private val depthController: DepthController?,
|
||||
private val successCallback: Consumer<Boolean>?
|
||||
) : IRemoteTransition.Stub() {
|
||||
|
||||
override fun startAnimation(
|
||||
token: IBinder,
|
||||
info: TransitionInfo,
|
||||
t: SurfaceControl.Transaction,
|
||||
finishCallback: IRemoteTransitionFinishedCallback
|
||||
) {
|
||||
val errorHandlingFinishCallback = Runnable {
|
||||
try {
|
||||
finishCallback.onTransitionFinished(null /* wct */, null /* sct */)
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Failed to call finish callback for desktop recents animation", e)
|
||||
}
|
||||
}
|
||||
|
||||
MAIN_EXECUTOR.execute {
|
||||
TaskViewUtils.composeRecentsDesktopLaunchAnimator(
|
||||
desktopTaskView,
|
||||
stateManager,
|
||||
depthController,
|
||||
info,
|
||||
t
|
||||
) {
|
||||
errorHandlingFinishCallback.run()
|
||||
successCallback?.accept(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun mergeAnimation(
|
||||
transition: IBinder,
|
||||
info: TransitionInfo,
|
||||
t: SurfaceControl.Transaction,
|
||||
mergeTarget: IBinder,
|
||||
finishCallback: IRemoteTransitionFinishedCallback
|
||||
) {}
|
||||
|
||||
override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "DesktopRecentsTransitionController"
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
|
||||
import static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorSet;
|
||||
@@ -41,6 +42,7 @@ import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.DragSource;
|
||||
import com.android.launcher3.DropTarget;
|
||||
import com.android.launcher3.Hotseat;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimationSuccessListener;
|
||||
@@ -59,7 +61,6 @@ import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.touch.ItemLongClickListener;
|
||||
import com.android.launcher3.uioverrides.PredictedAppIcon;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.launcher3.util.OnboardingPrefs;
|
||||
import com.android.launcher3.views.Snackbar;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
@@ -104,12 +105,11 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
if (mLauncher.getWorkspace().isSwitchingState()) return false;
|
||||
|
||||
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onWorkspaceItemLongClick");
|
||||
if (mEnableHotseatLongPressTipForTesting && !mLauncher.getOnboardingPrefs().getBoolean(
|
||||
OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN)) {
|
||||
if (mEnableHotseatLongPressTipForTesting && !HOTSEAT_LONGPRESS_TIP_SEEN.get(mLauncher)) {
|
||||
Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
|
||||
R.string.hotseat_prediction_settings, null,
|
||||
() -> mLauncher.startActivity(getSettingsIntent()));
|
||||
mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN);
|
||||
LauncherPrefs.get(mLauncher).put(HOTSEAT_LONGPRESS_TIP_SEEN, true);
|
||||
mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -67,6 +67,9 @@ public final class PredictionHelper {
|
||||
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
|
||||
return new AppTarget.Builder(new AppTargetId("folder:" + info.id),
|
||||
context.getPackageName(), info.user).build();
|
||||
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR) {
|
||||
return new AppTarget.Builder(new AppTargetId("app_pair:" + info.id),
|
||||
context.getPackageName(), info.user).build();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
*/
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
|
||||
import static com.android.launcher3.EncryptionType.ENCRYPTED;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
|
||||
import static com.android.launcher3.model.QuickstepModelDelegate.LAST_PREDICTION_ENABLED_STATE;
|
||||
import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
|
||||
|
||||
import android.app.prediction.AppTarget;
|
||||
@@ -29,6 +30,7 @@ import android.os.UserHandle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.ConstantItem;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||
@@ -47,6 +49,9 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public class PredictionUpdateTask extends BaseModelUpdateTask {
|
||||
|
||||
public static final ConstantItem<Boolean> LAST_PREDICTION_ENABLED =
|
||||
nonRestorableItem("last_prediction_enabled_state", true, ENCRYPTED);
|
||||
|
||||
private final List<AppTarget> mTargets;
|
||||
private final PredictorState mPredictorState;
|
||||
|
||||
@@ -61,8 +66,7 @@ public class PredictionUpdateTask extends BaseModelUpdateTask {
|
||||
Context context = app.getContext();
|
||||
|
||||
// TODO: remove this
|
||||
LauncherPrefs.getDevicePrefs(context).edit()
|
||||
.putBoolean(LAST_PREDICTION_ENABLED_STATE, !mTargets.isEmpty()).apply();
|
||||
LauncherPrefs.get(context).put(LAST_PREDICTION_ENABLED, !mTargets.isEmpty());
|
||||
|
||||
Set<UserHandle> usersForChangedShortcuts =
|
||||
dataModel.extraItems.get(mPredictorState.containerId).items.stream()
|
||||
|
||||
@@ -18,7 +18,8 @@ package com.android.launcher3.model;
|
||||
import static android.text.format.DateUtils.DAY_IN_MILLIS;
|
||||
import static android.text.format.DateUtils.formatElapsedTime;
|
||||
|
||||
import static com.android.launcher3.LauncherPrefs.getDevicePrefs;
|
||||
import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
|
||||
import static com.android.launcher3.EncryptionType.ENCRYPTED;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
|
||||
@@ -39,7 +40,6 @@ import android.app.prediction.AppTarget;
|
||||
import android.app.prediction.AppTargetEvent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.LauncherActivityInfo;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
@@ -55,8 +55,10 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.android.launcher3.ConstantItem;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.logger.LauncherAtom;
|
||||
import com.android.launcher3.logging.InstanceId;
|
||||
@@ -86,14 +88,15 @@ import java.util.stream.IntStream;
|
||||
*/
|
||||
public class QuickstepModelDelegate extends ModelDelegate {
|
||||
|
||||
public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
|
||||
private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS";
|
||||
private static final String BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets";
|
||||
private static final int NUM_OF_RECOMMENDED_WIDGETS_PREDICATION = 20;
|
||||
|
||||
private static final boolean IS_DEBUG = false;
|
||||
private static final String TAG = "QuickstepModelDelegate";
|
||||
|
||||
private static final ConstantItem<Long> LAST_SNAPSHOT_TIME_MILLIS =
|
||||
nonRestorableItem("LAST_SNAPSHOT_TIME_MILLIS", 0L, ENCRYPTED);
|
||||
|
||||
@VisibleForTesting
|
||||
final PredictorState mAllAppsState =
|
||||
new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
|
||||
@@ -211,8 +214,8 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
super.modelLoadComplete();
|
||||
|
||||
// Log snapshot of the model
|
||||
SharedPreferences prefs = getDevicePrefs(mApp.getContext());
|
||||
long lastSnapshotTimeMillis = prefs.getLong(LAST_SNAPSHOT_TIME_MILLIS, 0);
|
||||
LauncherPrefs prefs = LauncherPrefs.get(mApp.getContext());
|
||||
long lastSnapshotTimeMillis = prefs.get(LAST_SNAPSHOT_TIME_MILLIS);
|
||||
// Log snapshot only if previous snapshot was older than a day
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - lastSnapshotTimeMillis < DAY_IN_MILLIS) {
|
||||
@@ -233,7 +236,7 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId);
|
||||
}
|
||||
additionalSnapshotEvents(instanceId);
|
||||
prefs.edit().putLong(LAST_SNAPSHOT_TIME_MILLIS, now).apply();
|
||||
prefs.put(LAST_SNAPSHOT_TIME_MILLIS, now);
|
||||
}
|
||||
|
||||
// Only register for launcher snapshot logging if this is the primary ModelDelegate
|
||||
|
||||
+7
-9
@@ -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
|
||||
|
||||
+58
-20
@@ -15,8 +15,11 @@
|
||||
*/
|
||||
package com.android.launcher3.statehandlers;
|
||||
|
||||
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
|
||||
|
||||
import android.os.Debug;
|
||||
import android.os.SystemProperties;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
@@ -46,6 +49,7 @@ public class DesktopVisibilityController {
|
||||
|
||||
private boolean mFreeformTasksVisible;
|
||||
private boolean mInOverviewState;
|
||||
private boolean mBackgroundStateEnabled;
|
||||
private boolean mGestureInProgress;
|
||||
|
||||
@Nullable
|
||||
@@ -102,19 +106,15 @@ public class DesktopVisibilityController {
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether desktop mode is supported.
|
||||
*/
|
||||
private boolean isDesktopModeSupported() {
|
||||
return SystemProperties.getBoolean("persist.wm.debug.desktop_mode", false)
|
||||
|| SystemProperties.getBoolean("persist.wm.debug.desktop_mode_2", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether freeform windows are visible in desktop mode.
|
||||
*/
|
||||
public boolean areFreeformTasksVisible() {
|
||||
return mFreeformTasksVisible;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "areFreeformTasksVisible: freeformVisible=" + mFreeformTasksVisible
|
||||
+ " overview=" + mInOverviewState);
|
||||
}
|
||||
return mFreeformTasksVisible && !mInOverviewState;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,7 +122,8 @@ public class DesktopVisibilityController {
|
||||
*/
|
||||
public void setFreeformTasksVisible(boolean freeformTasksVisible) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "setFreeformTasksVisible: visible=" + freeformTasksVisible);
|
||||
Log.d(TAG, "setFreeformTasksVisible: visible=" + freeformTasksVisible
|
||||
+ " currentValue=" + mFreeformTasksVisible);
|
||||
}
|
||||
if (!isDesktopModeSupported()) {
|
||||
return;
|
||||
@@ -147,11 +148,21 @@ public class DesktopVisibilityController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the overview is visible and updates launcher visibility based on that.
|
||||
* Process launcher state change and update launcher view visibility based on desktop state
|
||||
*/
|
||||
public void setOverviewStateEnabled(boolean overviewStateEnabled) {
|
||||
public void onLauncherStateChanged(LauncherState state) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled);
|
||||
Log.d(TAG, "onLauncherStateChanged: newState=" + state);
|
||||
}
|
||||
setBackgroundStateEnabled(state == BACKGROUND_APP);
|
||||
// Desktop visibility tracks overview and background state separately
|
||||
setOverviewStateEnabled(state != BACKGROUND_APP && state.overviewUi);
|
||||
}
|
||||
|
||||
private void setOverviewStateEnabled(boolean overviewStateEnabled) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled
|
||||
+ " currentValue=" + mInOverviewState);
|
||||
}
|
||||
if (!isDesktopModeSupported()) {
|
||||
return;
|
||||
@@ -161,7 +172,30 @@ public class DesktopVisibilityController {
|
||||
if (mInOverviewState) {
|
||||
setLauncherViewsVisibility(View.VISIBLE);
|
||||
markLauncherResumed();
|
||||
} else if (mFreeformTasksVisible) {
|
||||
} else if (areFreeformTasksVisible() && !mGestureInProgress) {
|
||||
// Switching out of overview state and gesture finished.
|
||||
// If freeform tasks are still visible, hide launcher again.
|
||||
setLauncherViewsVisibility(View.INVISIBLE);
|
||||
markLauncherPaused();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setBackgroundStateEnabled(boolean backgroundStateEnabled) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "setBackgroundStateEnabled: enabled=" + backgroundStateEnabled
|
||||
+ " currentValue=" + mBackgroundStateEnabled);
|
||||
}
|
||||
if (!isDesktopModeSupported()) {
|
||||
return;
|
||||
}
|
||||
if (backgroundStateEnabled != mBackgroundStateEnabled) {
|
||||
mBackgroundStateEnabled = backgroundStateEnabled;
|
||||
if (mBackgroundStateEnabled) {
|
||||
setLauncherViewsVisibility(View.VISIBLE);
|
||||
markLauncherResumed();
|
||||
} else if (areFreeformTasksVisible() && !mGestureInProgress) {
|
||||
// Switching out of background state. If freeform tasks are visible, pause launcher.
|
||||
setLauncherViewsVisibility(View.INVISIBLE);
|
||||
markLauncherPaused();
|
||||
}
|
||||
@@ -182,6 +216,9 @@ public class DesktopVisibilityController {
|
||||
if (!isDesktopModeSupported()) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "setRecentsGestureStart");
|
||||
}
|
||||
setRecentsGestureInProgress(true);
|
||||
}
|
||||
|
||||
@@ -193,6 +230,9 @@ public class DesktopVisibilityController {
|
||||
if (!isDesktopModeSupported()) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "setRecentsGestureEnd: endTarget=" + endTarget);
|
||||
}
|
||||
setRecentsGestureInProgress(false);
|
||||
|
||||
if (endTarget == null) {
|
||||
@@ -202,9 +242,6 @@ public class DesktopVisibilityController {
|
||||
}
|
||||
|
||||
private void setRecentsGestureInProgress(boolean gestureInProgress) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "setGestureInProgress: inProgress=" + gestureInProgress);
|
||||
}
|
||||
if (gestureInProgress != mGestureInProgress) {
|
||||
mGestureInProgress = gestureInProgress;
|
||||
}
|
||||
@@ -221,7 +258,8 @@ public class DesktopVisibilityController {
|
||||
|
||||
private void setLauncherViewsVisibility(int visibility) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility);
|
||||
Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility + " "
|
||||
+ Debug.getCaller());
|
||||
}
|
||||
View workspaceView = mLauncher.getWorkspace();
|
||||
if (workspaceView != null) {
|
||||
@@ -235,7 +273,7 @@ public class DesktopVisibilityController {
|
||||
|
||||
private void markLauncherPaused() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "markLauncherPaused");
|
||||
Log.d(TAG, "markLauncherPaused " + Debug.getCaller());
|
||||
}
|
||||
StatefulActivity<LauncherState> activity =
|
||||
QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
|
||||
@@ -246,7 +284,7 @@ public class DesktopVisibilityController {
|
||||
|
||||
private void markLauncherResumed() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "markLauncherResumed");
|
||||
Log.d(TAG, "markLauncherResumed " + Debug.getCaller());
|
||||
}
|
||||
StatefulActivity<LauncherState> activity =
|
||||
QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
|
||||
|
||||
@@ -20,8 +20,6 @@ import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.util.OnboardingPrefs;
|
||||
import com.android.launcher3.util.Themes;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
|
||||
@@ -34,12 +32,10 @@ public abstract class BaseTaskbarContext extends ContextThemeWrapper implements
|
||||
|
||||
protected final LayoutInflater mLayoutInflater;
|
||||
private final List<OnDeviceProfileChangeListener> mDPChangeListeners = new ArrayList<>();
|
||||
private final OnboardingPrefs<BaseTaskbarContext> mOnboardingPrefs;
|
||||
|
||||
public BaseTaskbarContext(Context windowContext) {
|
||||
super(windowContext, Themes.getActivityThemeRes(windowContext));
|
||||
mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
|
||||
mOnboardingPrefs = new OnboardingPrefs<>(this, LauncherPrefs.getPrefs(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,11 +48,6 @@ public abstract class BaseTaskbarContext extends ContextThemeWrapper implements
|
||||
return mDPChangeListeners;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OnboardingPrefs<BaseTaskbarContext> getOnboardingPrefs() {
|
||||
return mOnboardingPrefs;
|
||||
}
|
||||
|
||||
/** Callback invoked when a drag is initiated within this context. */
|
||||
public abstract void onDragStart();
|
||||
|
||||
|
||||
+14
-2
@@ -18,11 +18,15 @@ package com.android.launcher3.taskbar;
|
||||
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_NOTIFICATIONS;
|
||||
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_QUICK_SETTINGS;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo.Config;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
|
||||
/**
|
||||
@@ -39,8 +43,8 @@ public class DesktopNavbarButtonsViewController extends NavbarButtonsViewControl
|
||||
private TaskbarControllers mControllers;
|
||||
|
||||
public DesktopNavbarButtonsViewController(TaskbarActivityContext context,
|
||||
FrameLayout navButtonsView) {
|
||||
super(context, navButtonsView);
|
||||
@Nullable Context navigationBarPanelContext, FrameLayout navButtonsView) {
|
||||
super(context, navigationBarPanelContext, navButtonsView);
|
||||
mContext = context;
|
||||
mNavButtonsView = navButtonsView;
|
||||
mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
|
||||
@@ -56,6 +60,11 @@ public class DesktopNavbarButtonsViewController extends NavbarButtonsViewControl
|
||||
@Override
|
||||
public void init(TaskbarControllers controllers) {
|
||||
mControllers = controllers;
|
||||
super.init(controllers);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupController() {
|
||||
mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarHeight;
|
||||
|
||||
// Quick settings and notifications buttons
|
||||
@@ -72,4 +81,7 @@ public class DesktopNavbarButtonsViewController extends NavbarButtonsViewControl
|
||||
/** Cleans up on destroy */
|
||||
@Override
|
||||
public void onDestroy() { }
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(@Config int configChanges) { }
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.pm.ActivityInfo;
|
||||
|
||||
@@ -28,7 +30,6 @@ import com.android.quickstep.LauncherActivityInterface;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
import com.android.quickstep.util.DesktopTask;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.quickstep.views.DesktopTaskView;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
|
||||
@@ -109,7 +110,7 @@ public final class KeyboardQuickSwitchController implements
|
||||
DesktopVisibilityController desktopController =
|
||||
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
|
||||
final boolean onDesktop =
|
||||
DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED
|
||||
isDesktopModeSupported()
|
||||
&& desktopController != null
|
||||
&& desktopController.areFreeformTasksVisible();
|
||||
|
||||
@@ -136,7 +137,7 @@ public final class KeyboardQuickSwitchController implements
|
||||
|
||||
// Hide all desktop tasks and show them on the hidden tile
|
||||
int hiddenDesktopTasks = 0;
|
||||
if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
|
||||
if (isDesktopModeSupported()) {
|
||||
DesktopTask desktopTask = findDesktopTask(tasks);
|
||||
if (desktopTask != null) {
|
||||
hiddenDesktopTasks = desktopTask.tasks.size();
|
||||
|
||||
@@ -40,6 +40,8 @@ import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import kotlin.Unit;
|
||||
|
||||
/**
|
||||
* A view that displays a recent task during a keyboard quick switch.
|
||||
*/
|
||||
@@ -96,17 +98,18 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
|
||||
Resources resources = mContext.getResources();
|
||||
|
||||
Preconditions.assertNotNull(mContent);
|
||||
mBorderAnimator = new BorderAnimator(
|
||||
mBorderAnimator = BorderAnimator.createScalingBorderAnimator(
|
||||
/* borderRadiusPx= */ resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_task_view_radius),
|
||||
/* borderColor= */ mBorderColor,
|
||||
/* borderAnimationParams= */ new BorderAnimator.ScalingParams(
|
||||
/* borderWidthPx= */ resources.getDimensionPixelSize(
|
||||
/* borderWidthPx= */ resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_border_width),
|
||||
/* boundsBuilder= */ bounds -> bounds.set(
|
||||
0, 0, getWidth(), getHeight()),
|
||||
/* targetView= */ this,
|
||||
/* contentView= */ mContent));
|
||||
/* boundsBuilder= */ bounds -> {
|
||||
bounds.set(0, 0, getWidth(), getHeight());
|
||||
return Unit.INSTANCE;
|
||||
},
|
||||
/* targetView= */ this,
|
||||
/* contentView= */ mContent,
|
||||
/* borderColor= */ mBorderColor);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -46,6 +46,8 @@ import com.android.app.animation.Interpolators;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -360,11 +362,8 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
OPEN_OUTLINE_INTERPOLATOR));
|
||||
}
|
||||
});
|
||||
if (currentFocusIndexOverride == -1) {
|
||||
initializeScroll(/* index= */ 0, /* shouldTruncateTarget= */ false);
|
||||
} else {
|
||||
animateFocusMove(-1, currentFocusIndexOverride);
|
||||
}
|
||||
animateFocusMove(-1, currentFocusIndexOverride == -1
|
||||
? Math.min(mContent.getChildCount(), 1) : currentFocusIndexOverride);
|
||||
displayedContent.setVisibility(VISIBLE);
|
||||
setVisibility(VISIBLE);
|
||||
requestFocus();
|
||||
@@ -413,7 +412,8 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
// there are more tasks
|
||||
initializeScroll(
|
||||
firstVisibleTaskIndex,
|
||||
/* shouldTruncateTarget= */ firstVisibleTaskIndex != toIndex);
|
||||
/* shouldTruncateTarget= */ firstVisibleTaskIndex != 0
|
||||
&& firstVisibleTaskIndex != toIndex);
|
||||
} else if (toIndex > fromIndex || toIndex == 0) {
|
||||
// Scrolling to next task view
|
||||
if (mIsRtl) {
|
||||
@@ -438,6 +438,13 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
focusAnimation.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
TestLogging.recordKeyEvent(
|
||||
TestProtocol.SEQUENCE_MAIN, "KeyboardQuickSwitchView key event", event);
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
return (mViewCallbacks != null
|
||||
@@ -454,56 +461,80 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
return;
|
||||
}
|
||||
if (mIsRtl) {
|
||||
scrollRightTo(
|
||||
task, shouldTruncateTarget, /* smoothScroll= */ false);
|
||||
} else {
|
||||
scrollLeftTo(
|
||||
task, shouldTruncateTarget, /* smoothScroll= */ false);
|
||||
task,
|
||||
shouldTruncateTarget,
|
||||
/* smoothScroll= */ false,
|
||||
/* waitForLayout= */ true);
|
||||
} else {
|
||||
scrollRightTo(
|
||||
task,
|
||||
shouldTruncateTarget,
|
||||
/* smoothScroll= */ false,
|
||||
/* waitForLayout= */ true);
|
||||
}
|
||||
}
|
||||
|
||||
private void scrollRightTo(@NonNull View targetTask) {
|
||||
scrollRightTo(targetTask, /* shouldTruncateTarget= */ false, /* smoothScroll= */ true);
|
||||
scrollRightTo(
|
||||
targetTask,
|
||||
/* shouldTruncateTarget= */ false,
|
||||
/* smoothScroll= */ true,
|
||||
/* waitForLayout= */ false);
|
||||
}
|
||||
|
||||
private void scrollRightTo(
|
||||
@NonNull View targetTask, boolean shouldTruncateTarget, boolean smoothScroll) {
|
||||
@NonNull View targetTask,
|
||||
boolean shouldTruncateTarget,
|
||||
boolean smoothScroll,
|
||||
boolean waitForLayout) {
|
||||
if (!mDisplayingRecentTasks) {
|
||||
return;
|
||||
}
|
||||
if (smoothScroll && !shouldScroll(targetTask, shouldTruncateTarget)) {
|
||||
return;
|
||||
}
|
||||
int scrollTo = targetTask.getLeft() - mSpacing
|
||||
+ (shouldTruncateTarget ? targetTask.getWidth() / 2 : 0);
|
||||
// Scroll so that the focused task is to the left of the list
|
||||
if (smoothScroll) {
|
||||
mScrollView.smoothScrollTo(scrollTo, 0);
|
||||
} else {
|
||||
mScrollView.scrollTo(scrollTo, 0);
|
||||
}
|
||||
runScrollCommand(waitForLayout, () -> {
|
||||
int scrollTo = targetTask.getLeft() - mSpacing
|
||||
+ (shouldTruncateTarget ? targetTask.getWidth() / 2 : 0);
|
||||
// Scroll so that the focused task is to the left of the list
|
||||
if (smoothScroll) {
|
||||
mScrollView.smoothScrollTo(scrollTo, 0);
|
||||
} else {
|
||||
mScrollView.scrollTo(scrollTo, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void scrollLeftTo(@NonNull View targetTask) {
|
||||
scrollLeftTo(targetTask, /* shouldTruncateTarget= */ false, /* smoothScroll= */ true);
|
||||
scrollLeftTo(
|
||||
targetTask,
|
||||
/* shouldTruncateTarget= */ false,
|
||||
/* smoothScroll= */ true,
|
||||
/* waitForLayout= */ false);
|
||||
}
|
||||
|
||||
private void scrollLeftTo(
|
||||
@NonNull View targetTask, boolean shouldTruncateTarget, boolean smoothScroll) {
|
||||
@NonNull View targetTask,
|
||||
boolean shouldTruncateTarget,
|
||||
boolean smoothScroll,
|
||||
boolean waitForLayout) {
|
||||
if (!mDisplayingRecentTasks) {
|
||||
return;
|
||||
}
|
||||
if (smoothScroll && !shouldScroll(targetTask, shouldTruncateTarget)) {
|
||||
return;
|
||||
}
|
||||
int scrollTo = targetTask.getRight() + mSpacing - mScrollView.getWidth()
|
||||
- (shouldTruncateTarget ? targetTask.getWidth() / 2 : 0);
|
||||
// Scroll so that the focused task is to the right of the list
|
||||
if (smoothScroll) {
|
||||
mScrollView.smoothScrollTo(scrollTo, 0);
|
||||
} else {
|
||||
mScrollView.scrollTo(scrollTo, 0);
|
||||
}
|
||||
runScrollCommand(waitForLayout, () -> {
|
||||
int scrollTo = targetTask.getRight() + mSpacing - mScrollView.getWidth()
|
||||
- (shouldTruncateTarget ? targetTask.getWidth() / 2 : 0);
|
||||
// Scroll so that the focused task is to the right of the list
|
||||
if (smoothScroll) {
|
||||
mScrollView.smoothScrollTo(scrollTo, 0);
|
||||
} else {
|
||||
mScrollView.scrollTo(scrollTo, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean shouldScroll(@NonNull View targetTask, boolean shouldTruncateTarget) {
|
||||
@@ -514,6 +545,21 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
return isTargetTruncated && !shouldTruncateTarget;
|
||||
}
|
||||
|
||||
private void runScrollCommand(boolean waitForLayout, @NonNull Runnable scrollCommand) {
|
||||
if (!waitForLayout) {
|
||||
scrollCommand.run();
|
||||
return;
|
||||
}
|
||||
mScrollView.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
scrollCommand.run();
|
||||
mScrollView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected KeyboardQuickSwitchTaskView getTaskAt(int index) {
|
||||
return !mDisplayingRecentTasks || index < 0 || index >= mContent.getChildCount()
|
||||
|
||||
+19
-18
@@ -24,7 +24,7 @@ import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.anim.AnimationSuccessListener;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
@@ -92,27 +92,19 @@ public class KeyboardQuickSwitchViewController {
|
||||
|
||||
protected void closeQuickSwitchView(boolean animate) {
|
||||
if (mCloseAnimation != null) {
|
||||
if (animate) {
|
||||
// Let currently-running animation finish.
|
||||
return;
|
||||
} else {
|
||||
mCloseAnimation.cancel();
|
||||
// Let currently-running animation finish.
|
||||
if (!animate) {
|
||||
mCloseAnimation.end();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!animate) {
|
||||
mCloseAnimation = null;
|
||||
onCloseComplete();
|
||||
return;
|
||||
}
|
||||
mCloseAnimation = mKeyboardQuickSwitchView.getCloseAnimation();
|
||||
|
||||
mCloseAnimation.addListener(new AnimationSuccessListener() {
|
||||
@Override
|
||||
public void onAnimationSuccess(Animator animator) {
|
||||
mCloseAnimation = null;
|
||||
onCloseComplete();
|
||||
}
|
||||
});
|
||||
mCloseAnimation.addListener(AnimatorListeners.forEndCallback(this::onCloseComplete));
|
||||
mCloseAnimation.start();
|
||||
}
|
||||
|
||||
@@ -141,11 +133,20 @@ public class KeyboardQuickSwitchViewController {
|
||||
GroupTask task = mControllerCallbacks.getTaskAt(index);
|
||||
if (task == null) {
|
||||
return Math.max(0, index);
|
||||
} else if (mOnDesktop) {
|
||||
}
|
||||
Task task2 = task.task2;
|
||||
int runningTaskId = ActivityManagerWrapper.getInstance().getRunningTask().taskId;
|
||||
if (runningTaskId == task.task1.key.id
|
||||
|| (task2 != null && runningTaskId == task2.key.id)) {
|
||||
// Ignore attempts to run the selected task if it is already running.
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (mOnDesktop) {
|
||||
UI_HELPER_EXECUTOR.execute(() ->
|
||||
SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
|
||||
.showDesktopApp(task.task1.key.id));
|
||||
} else if (task.task2 == null) {
|
||||
} else if (task2 == null) {
|
||||
UI_HELPER_EXECUTOR.execute(() ->
|
||||
ActivityManagerWrapper.getInstance().startActivityFromRecents(
|
||||
task.task1.key,
|
||||
@@ -153,13 +154,13 @@ public class KeyboardQuickSwitchViewController {
|
||||
taskView == null ? mKeyboardQuickSwitchView : taskView, null)
|
||||
.options));
|
||||
} else {
|
||||
mControllers.uiController.launchSplitTasks(
|
||||
taskView == null ? mKeyboardQuickSwitchView : taskView, task);
|
||||
mControllers.uiController.launchSplitTasks(task);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void onCloseComplete() {
|
||||
mCloseAnimation = null;
|
||||
mOverlayContext.getDragLayer().removeView(mKeyboardQuickSwitchView);
|
||||
mControllerCallbacks.onCloseComplete();
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ package com.android.launcher3.taskbar;
|
||||
import static com.android.launcher3.QuickstepTransitionManager.TRANSIENT_TASKBAR_TRANSITION_DURATION;
|
||||
import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
|
||||
import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES;
|
||||
import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_RESUMED;
|
||||
import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_VISIBLE;
|
||||
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
|
||||
|
||||
import android.animation.Animator;
|
||||
@@ -26,7 +26,6 @@ import android.animation.AnimatorSet;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.view.TaskTransitionSpec;
|
||||
import android.view.View;
|
||||
import android.view.WindowManagerGlobal;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -41,6 +40,7 @@ import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.logging.InstanceId;
|
||||
import com.android.launcher3.logging.InstanceIdSequence;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.MultiPropertyFactory;
|
||||
@@ -99,7 +99,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
|
||||
mLauncher.setTaskbarUIController(this);
|
||||
|
||||
onLauncherResumedOrPaused(mLauncher.hasBeenResumed(), true /* fromInit */);
|
||||
onLauncherVisibilityChanged(mLauncher.hasBeenResumed(), true /* fromInit */);
|
||||
|
||||
onStashedInAppChanged(mLauncher.getDeviceProfile());
|
||||
mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
|
||||
@@ -118,7 +118,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
onLauncherResumedOrPaused(false);
|
||||
onLauncherVisibilityChanged(false);
|
||||
mTaskbarLauncherStateController.onDestroy();
|
||||
|
||||
mLauncher.setTaskbarUIController(null);
|
||||
@@ -159,8 +159,9 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
* sub-animations are properly coordinated. This duration should not
|
||||
* actually be used since this animation tracks a swipe progress.
|
||||
*/
|
||||
protected void addLauncherResumeAnimation(AnimatorSet animation, int placeholderDuration) {
|
||||
animation.play(onLauncherResumedOrPaused(
|
||||
protected void addLauncherVisibilityChangedAnimation(AnimatorSet animation,
|
||||
int placeholderDuration) {
|
||||
animation.play(onLauncherVisibilityChanged(
|
||||
/* isResumed= */ true,
|
||||
/* fromInit= */ false,
|
||||
/* startAnimation= */ false,
|
||||
@@ -170,40 +171,53 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
/**
|
||||
* Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
|
||||
*/
|
||||
public void onLauncherResumedOrPaused(boolean isResumed) {
|
||||
onLauncherResumedOrPaused(isResumed, false /* fromInit */);
|
||||
public void onLauncherVisibilityChanged(boolean isVisible) {
|
||||
onLauncherVisibilityChanged(isVisible, false /* fromInit */);
|
||||
}
|
||||
|
||||
private void onLauncherResumedOrPaused(boolean isResumed, boolean fromInit) {
|
||||
onLauncherResumedOrPaused(
|
||||
isResumed,
|
||||
private void onLauncherVisibilityChanged(boolean isVisible, boolean fromInit) {
|
||||
onLauncherVisibilityChanged(
|
||||
isVisible,
|
||||
fromInit,
|
||||
/* startAnimation= */ true,
|
||||
DisplayController.isTransientTaskbar(mLauncher)
|
||||
? TRANSIENT_TASKBAR_TRANSITION_DURATION
|
||||
: (!isResumed
|
||||
: (!isVisible
|
||||
? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION
|
||||
: QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Animator onLauncherResumedOrPaused(
|
||||
boolean isResumed, boolean fromInit, boolean startAnimation, int duration) {
|
||||
private Animator onLauncherVisibilityChanged(
|
||||
boolean isVisible, boolean fromInit, boolean startAnimation, int duration) {
|
||||
// Launcher is resumed during the swipe-to-overview gesture under shell-transitions, so
|
||||
// avoid updating taskbar state in that situation (when it's non-interactive -- or
|
||||
// "background") to avoid premature animations.
|
||||
if (ENABLE_SHELL_TRANSITIONS && isResumed
|
||||
if (ENABLE_SHELL_TRANSITIONS && isVisible
|
||||
&& mLauncher.getStateManager().getState().hasFlag(FLAG_NON_INTERACTIVE)
|
||||
&& !mLauncher.getStateManager().getState().isTaskbarAlignedWithHotseat(mLauncher)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, isResumed);
|
||||
mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, isVisible);
|
||||
return mTaskbarLauncherStateController.applyState(fromInit ? 0 : duration, startAnimation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateTransitionCompletedAfterSwipeToHome(LauncherState state) {
|
||||
mTaskbarLauncherStateController.onStateTransitionCompletedAfterSwipeToHome(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshResumedState() {
|
||||
onLauncherResumedOrPaused(mLauncher.hasBeenResumed());
|
||||
onLauncherVisibilityChanged(mLauncher.hasBeenResumed());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void adjustHotseatForBubbleBar(boolean isBubbleBarVisible) {
|
||||
if (mLauncher.getHotseat() != null) {
|
||||
mLauncher.getHotseat().adjustForBubbleBar(isBubbleBarVisible);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,8 +293,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
|
||||
// Persistent features EDU tooltip.
|
||||
if (!DisplayController.isTransientTaskbar(mLauncher)) {
|
||||
return !mLauncher.getOnboardingPrefs().hasReachedMaxCount(
|
||||
OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP);
|
||||
return !OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP.hasReachedMax(mLauncher);
|
||||
}
|
||||
|
||||
// Transient swipe EDU tooltip.
|
||||
@@ -327,10 +340,25 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
return mTaskbarInAppDisplayProgress.value > 0;
|
||||
}
|
||||
|
||||
public boolean isBubbleBarEnabled() {
|
||||
return BubbleBarController.isBubbleBarEnabled();
|
||||
}
|
||||
|
||||
/** Whether the bubble bar has any bubbles. */
|
||||
public boolean hasBubbles() {
|
||||
if (mControllers == null) {
|
||||
return false;
|
||||
}
|
||||
if (mControllers.bubbleControllers.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return mControllers.bubbleControllers.get().bubbleBarViewController.hasBubbles();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExpandPip() {
|
||||
super.onExpandPip();
|
||||
mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, false);
|
||||
mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, false);
|
||||
mTaskbarLauncherStateController.applyState();
|
||||
}
|
||||
|
||||
@@ -362,8 +390,8 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchSplitTasks(@NonNull View taskView, @NonNull GroupTask groupTask) {
|
||||
mLauncher.launchSplitTasks(taskView, groupTask);
|
||||
public void launchSplitTasks(@NonNull GroupTask groupTask) {
|
||||
mLauncher.launchSplitTasks(groupTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,8 +23,10 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
|
||||
import static com.android.launcher3.LauncherAnimUtils.ROTATION_DRAWABLE_PERCENT;
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
|
||||
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
|
||||
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.SYSUI_SURFACE_PROGRESS_INDEX;
|
||||
import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
|
||||
import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
|
||||
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
|
||||
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
|
||||
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
|
||||
@@ -51,6 +53,7 @@ import android.animation.ObjectAnimator;
|
||||
import android.annotation.DrawableRes;
|
||||
import android.annotation.IdRes;
|
||||
import android.annotation.LayoutRes;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo.Config;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
@@ -78,6 +81,8 @@ import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.LauncherAnimUtils;
|
||||
import com.android.launcher3.R;
|
||||
@@ -88,10 +93,10 @@ import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
|
||||
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory;
|
||||
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter;
|
||||
import com.android.launcher3.util.DimensionUtils;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
|
||||
import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.launcher3.util.window.WindowManagerProxy;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
import com.android.systemui.shared.rotation.FloatingRotationButton;
|
||||
import com.android.systemui.shared.rotation.RotationButton;
|
||||
@@ -144,6 +149,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
private int mState;
|
||||
|
||||
private final TaskbarActivityContext mContext;
|
||||
private final @Nullable Context mNavigationBarPanelContext;
|
||||
private final WindowManagerProxy mWindowManagerProxy;
|
||||
private final FrameLayout mNavButtonsView;
|
||||
private final LinearLayout mNavButtonContainer;
|
||||
// Used for IME+A11Y buttons
|
||||
@@ -190,6 +197,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
private MultiValueAlpha mBackButtonAlpha;
|
||||
private MultiValueAlpha mHomeButtonAlpha;
|
||||
private FloatingRotationButton mFloatingRotationButton;
|
||||
private ImageView mImeSwitcherButton;
|
||||
|
||||
// Variables for moving nav buttons to a separate window above IME
|
||||
private boolean mAreNavButtonsInSeparateWindow = false;
|
||||
@@ -198,10 +206,12 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
this::onComputeInsetsForSeparateWindow;
|
||||
private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
|
||||
private ImageView mRecentsButton;
|
||||
private DisplayController mDisplayController;
|
||||
|
||||
public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
|
||||
public NavbarButtonsViewController(TaskbarActivityContext context,
|
||||
@Nullable Context navigationBarPanelContext, FrameLayout navButtonsView) {
|
||||
mContext = context;
|
||||
mNavigationBarPanelContext = navigationBarPanelContext;
|
||||
mWindowManagerProxy = WindowManagerProxy.INSTANCE.get(mContext);
|
||||
mNavButtonsView = navButtonsView;
|
||||
mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
|
||||
mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons);
|
||||
@@ -219,25 +229,27 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
*/
|
||||
public void init(TaskbarControllers controllers) {
|
||||
mControllers = controllers;
|
||||
setupController();
|
||||
}
|
||||
|
||||
protected void setupController() {
|
||||
boolean isThreeButtonNav = mContext.isThreeButtonNav();
|
||||
DeviceProfile deviceProfile = mContext.getDeviceProfile();
|
||||
Resources resources = mContext.getResources();
|
||||
Point p = !mContext.isUserSetupComplete()
|
||||
? new Point(0, controllers.taskbarActivityContext.getSetupWindowHeight())
|
||||
? new Point(0, mControllers.taskbarActivityContext.getSetupWindowHeight())
|
||||
: DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources,
|
||||
TaskbarManager.isPhoneMode(deviceProfile));
|
||||
mNavButtonsView.getLayoutParams().height = p.y;
|
||||
|
||||
mDisplayController = DisplayController.INSTANCE.get(mContext);
|
||||
|
||||
mIsImeRenderingNavButtons =
|
||||
InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
|
||||
if (!mIsImeRenderingNavButtons) {
|
||||
// IME switcher
|
||||
View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
|
||||
mImeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
|
||||
isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
|
||||
mControllers.navButtonController, R.id.ime_switcher);
|
||||
mPropertyHolders.add(new StatePropertyHolder(imeSwitcherButton,
|
||||
mPropertyHolders.add(new StatePropertyHolder(mImeSwitcherButton,
|
||||
flags -> ((flags & FLAG_SWITCHER_SHOWING) != 0)
|
||||
&& ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)));
|
||||
}
|
||||
@@ -306,7 +318,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
rotationButton.hide();
|
||||
mControllers.rotationButtonController.setRotationButton(rotationButton, null);
|
||||
} else {
|
||||
mFloatingRotationButton = new FloatingRotationButton(mContext,
|
||||
mFloatingRotationButton = new FloatingRotationButton(
|
||||
ENABLE_TASKBAR_NAVBAR_UNIFICATION ? mNavigationBarPanelContext : mContext,
|
||||
R.string.accessibility_rotate_button,
|
||||
R.layout.rotate_suggestion,
|
||||
R.id.rotate_suggestion,
|
||||
@@ -375,10 +388,12 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
int navButtonSize = mContext.getResources().getDimensionPixelSize(
|
||||
R.dimen.taskbar_nav_buttons_size);
|
||||
boolean isRtl = Utilities.isRtl(mContext.getResources());
|
||||
mPropertyHolders.add(new StatePropertyHolder(
|
||||
mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
|
||||
|| (flags & FLAG_KEYGUARD_VISIBLE) != 0,
|
||||
VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));
|
||||
if (!isPhoneMode(mContext.getDeviceProfile())) {
|
||||
mPropertyHolders.add(new StatePropertyHolder(
|
||||
mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
|
||||
|| (flags & FLAG_KEYGUARD_VISIBLE) != 0,
|
||||
VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));
|
||||
}
|
||||
|
||||
// home button
|
||||
mHomeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
|
||||
@@ -467,7 +482,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
/**
|
||||
* @return {@code true} if A11y is showing in 3 button nav taskbar
|
||||
*/
|
||||
private boolean isContextualButtonShowing() {
|
||||
private boolean isA11yButtonPersistent() {
|
||||
return mContext.isThreeButtonNav() && (mState & FLAG_A11Y_VISIBLE) != 0;
|
||||
}
|
||||
|
||||
@@ -730,12 +745,15 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
// TODO(b/244231596) we're getting the incorrect kidsMode value in small-screen
|
||||
boolean isInKidsMode = mContext.isNavBarKidsModeActive();
|
||||
|
||||
if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
|
||||
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
|
||||
NavButtonLayoutter navButtonLayoutter =
|
||||
NavButtonLayoutFactory.Companion.getUiLayoutter(
|
||||
dp, mNavButtonsView, res, isInKidsMode, isInSetup, isThreeButtonNav,
|
||||
TaskbarManager.isPhoneMode(dp), mDisplayController.getInfo().rotation);
|
||||
navButtonLayoutter.layoutButtons(dp, isContextualButtonShowing());
|
||||
dp, mNavButtonsView, mImeSwitcherButton,
|
||||
mControllers.rotationButtonController.getRotationButton(),
|
||||
mA11yButton, res, isInKidsMode, isInSetup, isThreeButtonNav,
|
||||
TaskbarManager.isPhoneMode(dp),
|
||||
mWindowManagerProxy.getRotation(mContext));
|
||||
navButtonLayoutter.layoutButtons(dp, isA11yButtonPersistent());
|
||||
updateNavButtonColor();
|
||||
return;
|
||||
}
|
||||
@@ -831,7 +849,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
int contextualWidth = mEndContextualContainer.getWidth();
|
||||
// If contextual buttons are showing, we check if the end margin is enough for the
|
||||
// contextual button to be showing - if not, move the nav buttons over a smidge
|
||||
if (isContextualButtonShowing() && navMarginEnd < contextualWidth) {
|
||||
if (isA11yButtonPersistent() && navMarginEnd < contextualWidth) {
|
||||
// Additional spacing, eat up half of space between last icon and nav button
|
||||
navMarginEnd += res.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT
|
||||
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
|
||||
Resources resources = mActivity.getResources();
|
||||
if (isPhoneGestureNavMode(mActivity.getDeviceProfile())) {
|
||||
mTaskbarSize = resources.getDimensionPixelSize(R.dimen.taskbar_size);
|
||||
mTaskbarSize = resources.getDimensionPixelSize(R.dimen.taskbar_phone_size);
|
||||
mStashedHandleWidth =
|
||||
resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen);
|
||||
} else {
|
||||
|
||||
@@ -20,17 +20,22 @@ import static android.os.Trace.TRACE_TAG_APP;
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
|
||||
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
|
||||
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
|
||||
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
|
||||
import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
|
||||
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY;
|
||||
import static com.android.launcher3.Flags.enableCursorHoverStates;
|
||||
import static com.android.launcher3.Utilities.calculateTextHeight;
|
||||
import static com.android.launcher3.Utilities.isRunningInTestHarness;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
|
||||
import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
|
||||
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
|
||||
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING;
|
||||
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_FULLSCREEN;
|
||||
import static com.android.launcher3.taskbar.TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW;
|
||||
import static com.android.launcher3.testing.shared.ResourceUtils.getBoolByName;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
|
||||
@@ -69,9 +74,11 @@ import androidx.annotation.VisibleForTesting;
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.apppairs.AppPairIcon;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.dot.DotInfo;
|
||||
import com.android.launcher3.folder.Folder;
|
||||
@@ -101,6 +108,7 @@ import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.touch.ItemClickHandler;
|
||||
import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy;
|
||||
import com.android.launcher3.util.ActivityOptionsWrapper;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.Executors;
|
||||
import com.android.launcher3.util.NavigationMode;
|
||||
@@ -109,6 +117,7 @@ import com.android.launcher3.util.RunnableList;
|
||||
import com.android.launcher3.util.SettingsCache;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
|
||||
import com.android.launcher3.util.TraceHelper;
|
||||
import com.android.launcher3.util.VibratorWrapper;
|
||||
import com.android.launcher3.util.ViewCache;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
@@ -121,7 +130,9 @@ import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
|
||||
@@ -136,6 +147,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
|
||||
private static final String WINDOW_TITLE = "Taskbar";
|
||||
|
||||
private final @Nullable Context mNavigationBarPanelContext;
|
||||
|
||||
private final TaskbarDragLayer mDragLayer;
|
||||
private final TaskbarControllers mControllers;
|
||||
|
||||
@@ -167,13 +180,20 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
|
||||
private final TaskbarShortcutMenuAccessibilityDelegate mAccessibilityDelegate;
|
||||
|
||||
public TaskbarActivityContext(Context windowContext, DeviceProfile launcherDp,
|
||||
private DeviceProfile mTransientTaskbarDeviceProfile;
|
||||
|
||||
private DeviceProfile mPersistentTaskbarDeviceProfile;
|
||||
|
||||
private final LauncherPrefs mLauncherPrefs;
|
||||
|
||||
public TaskbarActivityContext(Context windowContext,
|
||||
@Nullable Context navigationBarPanelContext, DeviceProfile launcherDp,
|
||||
TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
|
||||
unfoldTransitionProgressProvider) {
|
||||
super(windowContext);
|
||||
|
||||
mNavigationBarPanelContext = navigationBarPanelContext;
|
||||
applyDeviceProfile(launcherDp);
|
||||
|
||||
final Resources resources = getResources();
|
||||
|
||||
mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
|
||||
@@ -193,11 +213,16 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
Display display = windowContext.getDisplay();
|
||||
Context c = getApplicationContext();
|
||||
mWindowManager = c.getSystemService(WindowManager.class);
|
||||
mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
|
||||
mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
|
||||
|
||||
boolean phoneMode = TaskbarManager.isPhoneMode(mDeviceProfile);
|
||||
mLeftCorner = phoneMode
|
||||
? null
|
||||
: display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
|
||||
mRightCorner = phoneMode
|
||||
? null
|
||||
: display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
|
||||
|
||||
// Inflate views.
|
||||
boolean phoneMode = TaskbarManager.isPhoneMode(mDeviceProfile);
|
||||
int taskbarLayout = DisplayController.isTransientTaskbar(this) && !phoneMode
|
||||
? R.layout.transient_taskbar
|
||||
: R.layout.taskbar;
|
||||
@@ -215,7 +240,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
|
||||
// If Bubble bar is present, TaskbarControllers depends on it so build it first.
|
||||
Optional<BubbleControllers> bubbleControllersOptional = Optional.empty();
|
||||
if (BubbleBarController.BUBBLE_BAR_ENABLED && bubbleBarView != null) {
|
||||
BubbleBarController.onTaskbarRecreated();
|
||||
if (BubbleBarController.isBubbleBarEnabled() && bubbleBarView != null) {
|
||||
bubbleControllersOptional = Optional.of(new BubbleControllers(
|
||||
new BubbleBarController(this, bubbleBarView),
|
||||
new BubbleBarViewController(this, bubbleBarView),
|
||||
@@ -240,16 +266,18 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
new TaskbarDragController(this),
|
||||
buttonController,
|
||||
isDesktopMode
|
||||
? new DesktopNavbarButtonsViewController(this, navButtonsView)
|
||||
: new NavbarButtonsViewController(this, navButtonsView),
|
||||
? new DesktopNavbarButtonsViewController(this, mNavigationBarPanelContext,
|
||||
navButtonsView)
|
||||
: new NavbarButtonsViewController(this, mNavigationBarPanelContext,
|
||||
navButtonsView),
|
||||
rotationButtonController,
|
||||
new TaskbarDragLayerController(this, mDragLayer),
|
||||
new TaskbarViewController(this, taskbarView),
|
||||
new TaskbarScrimViewController(this, taskbarScrimView),
|
||||
new TaskbarUnfoldAnimationController(this, unfoldTransitionProgressProvider,
|
||||
mWindowManager,
|
||||
new RotationChangeProvider(c.getSystemService(DisplayManager.class), this,
|
||||
getMainThreadHandler())),
|
||||
mWindowManager,
|
||||
new RotationChangeProvider(c.getSystemService(DisplayManager.class), this,
|
||||
getMainThreadHandler())),
|
||||
new TaskbarKeyguardController(this),
|
||||
new StashedHandleViewController(this, stashedHandleView),
|
||||
new TaskbarStashController(this),
|
||||
@@ -267,8 +295,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
: TaskbarRecentAppsController.DEFAULT,
|
||||
new TaskbarEduTooltipController(this),
|
||||
new KeyboardQuickSwitchController(),
|
||||
new TaskbarDividerPopupController(this),
|
||||
new TaskbarPinningController(this),
|
||||
bubbleControllersOptional);
|
||||
|
||||
mLauncherPrefs = LauncherPrefs.get(this);
|
||||
}
|
||||
|
||||
/** Updates {@link DeviceProfile} instances for any Taskbar windows. */
|
||||
@@ -288,20 +318,42 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
* the icon size
|
||||
*/
|
||||
private void applyDeviceProfile(DeviceProfile originDeviceProfile) {
|
||||
mDeviceProfile = originDeviceProfile.toBuilder(this)
|
||||
.withDimensionsOverride(deviceProfile -> {
|
||||
// Taskbar should match the number of icons of hotseat
|
||||
deviceProfile.numShownHotseatIcons = originDeviceProfile.numShownHotseatIcons;
|
||||
// Same QSB width to have a smooth animation
|
||||
deviceProfile.hotseatQsbWidth = originDeviceProfile.hotseatQsbWidth;
|
||||
Consumer<DeviceProfile> overrideProvider = deviceProfile -> {
|
||||
// Taskbar should match the number of icons of hotseat
|
||||
deviceProfile.numShownHotseatIcons = originDeviceProfile.numShownHotseatIcons;
|
||||
// Same QSB width to have a smooth animation
|
||||
deviceProfile.hotseatQsbWidth = originDeviceProfile.hotseatQsbWidth;
|
||||
|
||||
// Update icon size
|
||||
deviceProfile.iconSizePx = deviceProfile.taskbarIconSize;
|
||||
deviceProfile.updateIconSize(1f, getResources());
|
||||
}).build();
|
||||
// Update icon size
|
||||
deviceProfile.iconSizePx = deviceProfile.taskbarIconSize;
|
||||
deviceProfile.updateIconSize(1f, getResources());
|
||||
};
|
||||
mDeviceProfile = originDeviceProfile.toBuilder(this)
|
||||
.withDimensionsOverride(overrideProvider).build();
|
||||
|
||||
if (DisplayController.isTransientTaskbar(this)) {
|
||||
mTransientTaskbarDeviceProfile = mDeviceProfile;
|
||||
mPersistentTaskbarDeviceProfile = mDeviceProfile
|
||||
.toBuilder(this)
|
||||
.withDimensionsOverride(overrideProvider)
|
||||
.setIsTransientTaskbar(false)
|
||||
.build();
|
||||
} else {
|
||||
mPersistentTaskbarDeviceProfile = mDeviceProfile;
|
||||
mTransientTaskbarDeviceProfile = mDeviceProfile
|
||||
.toBuilder(this)
|
||||
.withDimensionsOverride(overrideProvider)
|
||||
.setIsTransientTaskbar(true)
|
||||
.build();
|
||||
}
|
||||
mNavMode = DisplayController.getNavigationMode(this);
|
||||
}
|
||||
|
||||
/** Called when the visibility of the bubble bar changed. */
|
||||
public void bubbleBarVisibilityChanged(boolean isVisible) {
|
||||
mControllers.uiController.adjustHotseatForBubbleBar(isVisible);
|
||||
mControllers.taskbarViewController.resetIconAlignmentController();
|
||||
}
|
||||
|
||||
public void init(@NonNull TaskbarSharedState sharedState) {
|
||||
mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, getResources(), false);
|
||||
@@ -320,18 +372,18 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
sharedState.systemBarAttrsBehavior);
|
||||
onNavButtonsDarkIntensityChanged(sharedState.navButtonsDarkIntensity);
|
||||
|
||||
if (FLAG_HIDE_NAVBAR_WINDOW) {
|
||||
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
|
||||
// W/ the flag not set this entire class gets re-created, which resets the value of
|
||||
// mIsDestroyed. We re-use the class for small-screen, so we explicitly have to mark
|
||||
// this class as non-destroyed
|
||||
mIsDestroyed = false;
|
||||
}
|
||||
|
||||
if (!mAddedWindow) {
|
||||
if (!enableTaskbarNoRecreate() && !mAddedWindow) {
|
||||
mWindowManager.addView(mDragLayer, mWindowLayoutParams);
|
||||
mAddedWindow = true;
|
||||
} else {
|
||||
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
|
||||
notifyUpdateLayoutParams();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,6 +399,11 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
mControllers.taskbarAllAppsController.toggle();
|
||||
}
|
||||
|
||||
/** Toggles Taskbar All Apps overlay with keyboard ready for search. */
|
||||
public void toggleAllAppsSearch() {
|
||||
mControllers.taskbarAllAppsController.toggleSearch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceProfile getDeviceProfile() {
|
||||
return mDeviceProfile;
|
||||
@@ -359,6 +416,11 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
getDeviceProfile().toSmallString());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public LauncherPrefs getLauncherPrefs() {
|
||||
return mLauncherPrefs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the View bounds of transient taskbar.
|
||||
*/
|
||||
@@ -374,7 +436,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
|
||||
/**
|
||||
* Creates LayoutParams for adding a view directly to WindowManager as a new window.
|
||||
* @param type The window type to pass to the created WindowManager.LayoutParams.
|
||||
*
|
||||
* @param type The window type to pass to the created WindowManager.LayoutParams.
|
||||
* @param title The window title to pass to the created WindowManager.LayoutParams.
|
||||
*/
|
||||
public WindowManager.LayoutParams createDefaultWindowLayoutParams(int type, String title) {
|
||||
@@ -413,9 +476,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
* for taskbar showing as navigation bar
|
||||
*/
|
||||
private WindowManager.LayoutParams createAllWindowParams() {
|
||||
final int windowType =
|
||||
ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL;
|
||||
WindowManager.LayoutParams windowLayoutParams =
|
||||
createDefaultWindowLayoutParams(TYPE_NAVIGATION_BAR_PANEL,
|
||||
TaskbarActivityContext.WINDOW_TITLE);
|
||||
createDefaultWindowLayoutParams(windowType, TaskbarActivityContext.WINDOW_TITLE);
|
||||
boolean isPhoneNavMode = TaskbarManager.isPhoneButtonNavMode(this);
|
||||
if (!isPhoneNavMode) {
|
||||
return windowLayoutParams;
|
||||
@@ -428,7 +492,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
windowLayoutParams.paramsForRotation = new WindowManager.LayoutParams[4];
|
||||
for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
|
||||
WindowManager.LayoutParams lp =
|
||||
createDefaultWindowLayoutParams(TYPE_NAVIGATION_BAR_PANEL,
|
||||
createDefaultWindowLayoutParams(windowType,
|
||||
TaskbarActivityContext.WINDOW_TITLE);
|
||||
switch (rot) {
|
||||
case Surface.ROTATION_0, Surface.ROTATION_180 -> {
|
||||
@@ -678,7 +742,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
mIsDestroyed = true;
|
||||
setUIController(TaskbarUIController.DEFAULT);
|
||||
mControllers.onDestroy();
|
||||
if (!FLAG_HIDE_NAVBAR_WINDOW) {
|
||||
if (!enableTaskbarNoRecreate() && !ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
|
||||
mWindowManager.removeViewImmediate(mDragLayer);
|
||||
mAddedWindow = false;
|
||||
}
|
||||
@@ -777,7 +841,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
// Overlay AFVs are in a separate window and do not require Taskbar to be fullscreen.
|
||||
if (!isDragInProgress
|
||||
&& !AbstractFloatingView.hasOpenView(
|
||||
this, TYPE_ALL & ~TYPE_TASKBAR_OVERLAY_PROXY)) {
|
||||
this, TYPE_ALL & ~TYPE_TASKBAR_OVERLAY_PROXY)) {
|
||||
// Reverts Taskbar window to its original size
|
||||
setTaskbarWindowFullscreen(false);
|
||||
}
|
||||
@@ -810,7 +874,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
}
|
||||
mWindowLayoutParams.height = height;
|
||||
mControllers.taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
|
||||
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
|
||||
notifyUpdateLayoutParams();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -819,9 +883,9 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
public int getDefaultTaskbarWindowHeight() {
|
||||
Resources resources = getResources();
|
||||
|
||||
if (FLAG_HIDE_NAVBAR_WINDOW && mDeviceProfile.isPhone) {
|
||||
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION && mDeviceProfile.isPhone) {
|
||||
return isThreeButtonNav() ?
|
||||
resources.getDimensionPixelSize(R.dimen.taskbar_size) :
|
||||
resources.getDimensionPixelSize(R.dimen.taskbar_phone_size) :
|
||||
resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
|
||||
}
|
||||
|
||||
@@ -829,20 +893,46 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
return getSetupWindowHeight();
|
||||
}
|
||||
|
||||
if (DisplayController.isTransientTaskbar(this)) {
|
||||
return mDeviceProfile.taskbarHeight
|
||||
+ (2 * mDeviceProfile.taskbarBottomMargin)
|
||||
+ resources.getDimensionPixelSize(R.dimen.transient_taskbar_shadow_blur);
|
||||
boolean shouldTreatAsTransient = DisplayController.isTransientTaskbar(this)
|
||||
|| (enableTaskbarPinning() && !isThreeButtonNav());
|
||||
|
||||
int extraHeightForTaskbarTooltips = enableCursorHoverStates()
|
||||
? resources.getDimensionPixelSize(R.dimen.arrow_toast_arrow_height)
|
||||
+ (resources.getDimensionPixelSize(R.dimen.taskbar_tooltip_vertical_padding) * 2)
|
||||
+ calculateTextHeight(
|
||||
resources.getDimensionPixelSize(R.dimen.arrow_toast_text_size))
|
||||
: 0;
|
||||
|
||||
// Return transient taskbar window height when pinning feature is enabled, so taskbar view
|
||||
// does not get cut off during pinning animation.
|
||||
if (shouldTreatAsTransient) {
|
||||
DeviceProfile transientTaskbarDp = mDeviceProfile.toBuilder(this)
|
||||
.setIsTransientTaskbar(true).build();
|
||||
|
||||
return transientTaskbarDp.taskbarHeight
|
||||
+ (2 * transientTaskbarDp.taskbarBottomMargin)
|
||||
+ Math.max(extraHeightForTaskbarTooltips, resources.getDimensionPixelSize(
|
||||
R.dimen.transient_taskbar_shadow_blur));
|
||||
}
|
||||
|
||||
|
||||
return mDeviceProfile.taskbarHeight
|
||||
+ Math.max(getLeftCornerRadius(), getRightCornerRadius());
|
||||
+ Math.max(getLeftCornerRadius(), getRightCornerRadius())
|
||||
+ extraHeightForTaskbarTooltips;
|
||||
}
|
||||
|
||||
public int getSetupWindowHeight() {
|
||||
return getResources().getDimensionPixelSize(R.dimen.taskbar_suw_frame);
|
||||
}
|
||||
|
||||
public DeviceProfile getTransientTaskbarDeviceProfile() {
|
||||
return mTransientTaskbarDeviceProfile;
|
||||
}
|
||||
|
||||
public DeviceProfile getPersistentTaskbarDeviceProfile() {
|
||||
return mPersistentTaskbarDeviceProfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Either adds or removes {@link WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} on the taskbar
|
||||
* window.
|
||||
@@ -853,7 +943,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
} else {
|
||||
mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE;
|
||||
}
|
||||
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
|
||||
notifyUpdateLayoutParams();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -904,6 +994,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
}
|
||||
|
||||
protected void onTaskbarIconClicked(View view) {
|
||||
TaskbarUIController taskbarUIController = mControllers.uiController;
|
||||
RecentsView recents = taskbarUIController.getRecentsView();
|
||||
boolean shouldCloseAllOpenViews = true;
|
||||
Object tag = view.getTag();
|
||||
if (tag instanceof Task) {
|
||||
@@ -911,41 +1003,26 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
|
||||
ActivityOptions.makeBasic());
|
||||
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
|
||||
} else if (tag instanceof FolderInfo) {
|
||||
} else if (tag instanceof FolderInfo fi && fi.itemType == Favorites.ITEM_TYPE_FOLDER) {
|
||||
// Tapping an expandable folder icon on Taskbar
|
||||
shouldCloseAllOpenViews = false;
|
||||
FolderIcon folderIcon = (FolderIcon) view;
|
||||
Folder folder = folderIcon.getFolder();
|
||||
|
||||
folder.setOnFolderStateChangedListener(newState -> {
|
||||
if (newState == Folder.STATE_OPEN) {
|
||||
setTaskbarWindowFocusableForIme(true);
|
||||
} else if (newState == Folder.STATE_CLOSED) {
|
||||
// Defer by a frame to ensure we're no longer fullscreen and thus won't jump.
|
||||
getDragLayer().post(() -> setTaskbarWindowFocusableForIme(false));
|
||||
folder.setOnFolderStateChangedListener(null);
|
||||
}
|
||||
});
|
||||
|
||||
setTaskbarWindowFullscreen(true);
|
||||
|
||||
getDragLayer().post(() -> {
|
||||
folder.animateOpen();
|
||||
getStatsLogManager().logger().withItemInfo(folder.mInfo).log(LAUNCHER_FOLDER_OPEN);
|
||||
|
||||
folder.iterateOverItems((itemInfo, itemView) -> {
|
||||
mControllers.taskbarViewController
|
||||
.setClickAndLongClickListenersForIcon(itemView);
|
||||
// To play haptic when dragging, like other Taskbar items do.
|
||||
itemView.setHapticFeedbackEnabled(true);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
expandFolder((FolderIcon) view);
|
||||
} else if (tag instanceof FolderInfo fi && fi.itemType == Favorites.ITEM_TYPE_APP_PAIR) {
|
||||
// Tapping an app pair icon on Taskbar
|
||||
if (recents != null && recents.isSplitSelectionActive()) {
|
||||
// TODO (b/274835596): Implement "can't split with this" bounce animation
|
||||
Toast.makeText(this, "Unable to split with an app pair. Select another app.",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
// Else launch the selected app pair
|
||||
launchFromTaskbarPreservingSplitIfVisible(recents, view, fi.contents);
|
||||
mControllers.uiController.onTaskbarIconLaunched(fi);
|
||||
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
|
||||
}
|
||||
} else if (tag instanceof WorkspaceItemInfo) {
|
||||
// Tapping a launchable icon on Taskbar
|
||||
WorkspaceItemInfo info = (WorkspaceItemInfo) tag;
|
||||
if (!info.isDisabled() || !ItemClickHandler.handleDisabledItemClicked(info, this)) {
|
||||
TaskbarUIController taskbarUIController = mControllers.uiController;
|
||||
RecentsView recents = taskbarUIController.getRecentsView();
|
||||
if (recents != null && recents.isSplitSelectionActive()) {
|
||||
// If we are selecting a second app for split, launch the split tasks
|
||||
taskbarUIController.triggerSecondAppForSplit(info, info.intent, view);
|
||||
@@ -973,7 +1050,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
getSystemService(LauncherApps.class)
|
||||
.startShortcut(packageName, id, null, null, info.user);
|
||||
} else {
|
||||
launchFromTaskbarPreservingSplitIfVisible(recents, info);
|
||||
launchFromTaskbarPreservingSplitIfVisible(
|
||||
recents, view, Collections.singletonList(info));
|
||||
}
|
||||
|
||||
} catch (NullPointerException
|
||||
@@ -984,7 +1062,21 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
Log.e(TAG, "Unable to launch. tag=" + info + " intent=" + intent, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If the app was launched from a folder, stash the taskbar after it closes
|
||||
Folder f = Folder.getOpen(this);
|
||||
if (f != null && f.getInfo().id == info.container) {
|
||||
f.addOnFolderStateChangedListener(new Folder.OnFolderStateChangedListener() {
|
||||
@Override
|
||||
public void onFolderStateChanged(int newState) {
|
||||
if (newState == Folder.STATE_CLOSED) {
|
||||
f.removeOnFolderStateChangedListener(this);
|
||||
mControllers.taskbarStashController
|
||||
.updateAndAnimateTransientTaskbar(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
mControllers.uiController.onTaskbarIconLaunched(info);
|
||||
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
|
||||
@@ -992,14 +1084,13 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
} else if (tag instanceof AppInfo) {
|
||||
// Tapping an item in AllApps
|
||||
AppInfo info = (AppInfo) tag;
|
||||
TaskbarUIController taskbarUIController = mControllers.uiController;
|
||||
RecentsView recents = taskbarUIController.getRecentsView();
|
||||
if (recents != null
|
||||
&& taskbarUIController.getRecentsView().isSplitSelectionActive()) {
|
||||
// If we are selecting a second app for split, launch the split tasks
|
||||
taskbarUIController.triggerSecondAppForSplit(info, info.intent, view);
|
||||
} else {
|
||||
launchFromTaskbarPreservingSplitIfVisible(recents, info);
|
||||
launchFromTaskbarPreservingSplitIfVisible(
|
||||
recents, view, Collections.singletonList(info));
|
||||
}
|
||||
mControllers.uiController.onTaskbarIconLaunched(info);
|
||||
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
|
||||
@@ -1021,17 +1112,22 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
* (potentially breaking a split pair).
|
||||
*/
|
||||
private void launchFromTaskbarPreservingSplitIfVisible(@Nullable RecentsView recents,
|
||||
ItemInfo info) {
|
||||
@Nullable View launchingIconView, List<? extends ItemInfo> itemInfos) {
|
||||
if (recents == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean findExactPairMatch = itemInfos.size() == 2;
|
||||
// Convert the list of ItemInfo instances to a list of ComponentKeys
|
||||
List<ComponentKey> componentKeys =
|
||||
itemInfos.stream().map(ItemInfo::getComponentKey).toList();
|
||||
recents.getSplitSelectController().findLastActiveTasksAndRunCallback(
|
||||
Collections.singletonList(info.getComponentKey()),
|
||||
componentKeys,
|
||||
findExactPairMatch,
|
||||
foundTasks -> {
|
||||
@Nullable Task foundTask = foundTasks.get(0);
|
||||
if (foundTask != null) {
|
||||
TaskView foundTaskView =
|
||||
recents.getTaskViewByTaskId(foundTask.key.id);
|
||||
TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
|
||||
if (foundTaskView != null
|
||||
&& foundTaskView.isVisibleToUser()) {
|
||||
TestLogging.recordEvent(
|
||||
@@ -1040,8 +1136,16 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
return;
|
||||
}
|
||||
}
|
||||
startItemInfoActivity(info);
|
||||
});
|
||||
|
||||
if (findExactPairMatch) {
|
||||
// We did not find the app pair we were looking for, so launch one.
|
||||
recents.getSplitSelectController().getAppPairsController().launchAppPair(
|
||||
(AppPairIcon) launchingIconView);
|
||||
} else {
|
||||
startItemInfoActivity(itemInfos.get(0));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void startItemInfoActivity(ItemInfo info) {
|
||||
@@ -1063,6 +1167,41 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
}
|
||||
}
|
||||
|
||||
/** Expands a folder icon when it is clicked */
|
||||
private void expandFolder(FolderIcon folderIcon) {
|
||||
Folder folder = folderIcon.getFolder();
|
||||
|
||||
folder.setPriorityOnFolderStateChangedListener(
|
||||
new Folder.OnFolderStateChangedListener() {
|
||||
@Override
|
||||
public void onFolderStateChanged(int newState) {
|
||||
if (newState == Folder.STATE_OPEN) {
|
||||
setTaskbarWindowFocusableForIme(true);
|
||||
} else if (newState == Folder.STATE_CLOSED) {
|
||||
// Defer by a frame to ensure we're no longer fullscreen and thus
|
||||
// won't jump.
|
||||
getDragLayer().post(() -> setTaskbarWindowFocusableForIme(false));
|
||||
folder.setPriorityOnFolderStateChangedListener(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setTaskbarWindowFullscreen(true);
|
||||
|
||||
getDragLayer().post(() -> {
|
||||
folder.animateOpen();
|
||||
getStatsLogManager().logger().withItemInfo(folder.mInfo).log(LAUNCHER_FOLDER_OPEN);
|
||||
|
||||
folder.iterateOverItems((itemInfo, itemView) -> {
|
||||
mControllers.taskbarViewController
|
||||
.setClickAndLongClickListenersForIcon(itemView);
|
||||
// To play haptic when dragging, like other Taskbar items do.
|
||||
itemView.setHapticFeedbackEnabled(true);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the taskbar is currently visually stashed.
|
||||
*/
|
||||
@@ -1070,19 +1209,11 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
return mControllers.taskbarStashController.isStashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we detect a long press in the nav region before passing the gesture slop.
|
||||
*
|
||||
* @return Whether taskbar handled the long press, and thus should cancel the gesture.
|
||||
*/
|
||||
public boolean onLongPressToUnstashTaskbar() {
|
||||
return mControllers.taskbarStashController.onLongPressToUnstashTaskbar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we want to unstash taskbar when user performs swipes up gesture.
|
||||
*/
|
||||
public void onSwipeToUnstashTaskbar() {
|
||||
VibratorWrapper.INSTANCE.get(this).vibrateForTaskbarUnstash();
|
||||
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(/* stash= */ false);
|
||||
mControllers.taskbarEduTooltipController.hide();
|
||||
}
|
||||
@@ -1133,28 +1264,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
* @param animateForward Whether to animate towards the unstashed hint state or back to stashed.
|
||||
*/
|
||||
public void startTaskbarUnstashHint(boolean animateForward) {
|
||||
// TODO(b/270395798): Clean up forceUnstash after removing long-press unstashing code.
|
||||
startTaskbarUnstashHint(animateForward, /* forceUnstash = */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we detect a motion down or up/cancel in the nav region while stashed.
|
||||
*
|
||||
* @param animateForward Whether to animate towards the unstashed hint state or back to stashed.
|
||||
* @param forceUnstash Whether we force the unstash hint.
|
||||
*/
|
||||
public void startTaskbarUnstashHint(boolean animateForward, boolean forceUnstash) {
|
||||
// TODO(b/270395798): Clean up forceUnstash after removing long-press unstashing code.
|
||||
mControllers.taskbarStashController.startUnstashHint(animateForward, forceUnstash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables manual taskbar stashing. This method should only be used for tests that need to
|
||||
* stash/unstash the taskbar.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void enableManualStashingDuringTests(boolean enableManualStashing) {
|
||||
mControllers.taskbarStashController.enableManualStashingDuringTests(enableManualStashing);
|
||||
mControllers.taskbarStashController.startUnstashHint(animateForward);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1167,15 +1277,12 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* Unstashes the Taskbar if it is stashed. This method should only be used to unstash the
|
||||
* taskbar at the end of a test.
|
||||
* Unstashes the Taskbar if it is stashed.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void unstashTaskbarIfStashed() {
|
||||
if (DisplayController.isTransientTaskbar(this)) {
|
||||
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(false);
|
||||
} else {
|
||||
mControllers.taskbarStashController.onLongPressToUnstashTaskbar();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1212,7 +1319,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
|
||||
TaskbarUIController uiController = mControllers.uiController;
|
||||
if (uiController instanceof LauncherTaskbarUIController) {
|
||||
((LauncherTaskbarUIController) uiController).addLauncherResumeAnimation(
|
||||
((LauncherTaskbarUIController) uiController).addLauncherVisibilityChangedAnimation(
|
||||
fullAnimation, duration);
|
||||
}
|
||||
mControllers.taskbarStashController.addUnstashToHotseatAnimation(fullAnimation, duration);
|
||||
@@ -1249,12 +1356,16 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
mWindowLayoutParams.privateFlags &=
|
||||
~WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
|
||||
}
|
||||
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
|
||||
notifyUpdateLayoutParams();
|
||||
}
|
||||
|
||||
void notifyUpdateLayoutParams() {
|
||||
if (mDragLayer.isAttachedToWindow()) {
|
||||
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
|
||||
if (enableTaskbarNoRecreate()) {
|
||||
mWindowManager.updateViewLayout(mDragLayer.getRootView(), mWindowLayoutParams);
|
||||
} else {
|
||||
mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,26 +29,39 @@ import com.android.launcher3.Utilities
|
||||
import com.android.launcher3.Utilities.mapRange
|
||||
import com.android.launcher3.Utilities.mapToRange
|
||||
import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
|
||||
import com.android.launcher3.taskbar.TaskbarPinningController.Companion.PINNING_PERSISTENT
|
||||
import com.android.launcher3.taskbar.TaskbarPinningController.Companion.PINNING_TRANSIENT
|
||||
import com.android.launcher3.util.DisplayController
|
||||
import kotlin.math.min
|
||||
|
||||
/** Helps draw the taskbar background, made up of a rectangle plus two inverted rounded corners. */
|
||||
class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
|
||||
class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
|
||||
|
||||
private val isInSetup: Boolean = !context.isUserSetupComplete
|
||||
private val DARK_THEME_SHADOW_ALPHA = 51f
|
||||
private val LIGHT_THEME_SHADOW_ALPHA = 25f
|
||||
|
||||
private val maxTransientTaskbarHeight =
|
||||
context.transientTaskbarDeviceProfile.taskbarHeight.toFloat()
|
||||
private val maxPersistentTaskbarHeight =
|
||||
context.persistentTaskbarDeviceProfile.taskbarHeight.toFloat()
|
||||
var backgroundProgress =
|
||||
if (DisplayController.isTransientTaskbar(context)) {
|
||||
PINNING_TRANSIENT
|
||||
} else {
|
||||
PINNING_PERSISTENT
|
||||
}
|
||||
|
||||
var isAnimatingPinning = false
|
||||
|
||||
val paint = Paint()
|
||||
val lastDrawnTransientRect = RectF()
|
||||
var backgroundHeight = context.deviceProfile.taskbarHeight.toFloat()
|
||||
var translationYForSwipe = 0f
|
||||
var translationYForStash = 0f
|
||||
|
||||
private var maxBackgroundHeight = context.deviceProfile.taskbarHeight.toFloat()
|
||||
private val transientBackgroundBounds = context.transientTaskbarBounds
|
||||
|
||||
private val isTransientTaskbar = DisplayController.isTransientTaskbar(context)
|
||||
|
||||
private val shadowAlpha: Float
|
||||
private var shadowBlur = 0f
|
||||
private var keyShadowDistance = 0f
|
||||
@@ -75,13 +88,6 @@ class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
|
||||
paint.flags = Paint.ANTI_ALIAS_FLAG
|
||||
paint.style = Paint.Style.FILL
|
||||
|
||||
if (isTransientTaskbar) {
|
||||
val res = context.resources
|
||||
bottomMargin = res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin)
|
||||
shadowBlur = res.getDimension(R.dimen.transient_taskbar_shadow_blur)
|
||||
keyShadowDistance = res.getDimension(R.dimen.transient_taskbar_key_shadow_distance)
|
||||
}
|
||||
|
||||
shadowAlpha =
|
||||
if (Utilities.isDarkTheme(context)) DARK_THEME_SHADOW_ALPHA
|
||||
else LIGHT_THEME_SHADOW_ALPHA
|
||||
@@ -90,10 +96,11 @@ class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
|
||||
}
|
||||
|
||||
fun updateStashedHandleWidth(dp: DeviceProfile, res: Resources) {
|
||||
stashedHandleWidth = res.getDimensionPixelSize(
|
||||
stashedHandleWidth =
|
||||
res.getDimensionPixelSize(
|
||||
if (TaskbarManager.isPhoneMode(dp)) R.dimen.taskbar_stashed_small_screen
|
||||
else R.dimen.taskbar_stashed_handle_width
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,7 +109,7 @@ class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
|
||||
* @param cornerRoundness 0 has no round corner, 1 has complete round corner.
|
||||
*/
|
||||
fun setCornerRoundness(cornerRoundness: Float) {
|
||||
if (isTransientTaskbar && !transientBackgroundBounds.isEmpty) {
|
||||
if (DisplayController.isTransientTaskbar(context) && !transientBackgroundBounds.isEmpty) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -126,63 +133,123 @@ class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
|
||||
|
||||
/** Draws the background with the given paint and height, on the provided canvas. */
|
||||
fun draw(canvas: Canvas) {
|
||||
if (isInSetup) return
|
||||
val isTransientTaskbar = backgroundProgress == 0f
|
||||
canvas.save()
|
||||
if (!isTransientTaskbar || transientBackgroundBounds.isEmpty) {
|
||||
canvas.translate(0f, canvas.height - backgroundHeight - bottomMargin)
|
||||
// Draw the background behind taskbar content.
|
||||
canvas.drawRect(0f, 0f, canvas.width.toFloat(), backgroundHeight, paint)
|
||||
|
||||
// Draw the inverted rounded corners above the taskbar.
|
||||
canvas.translate(0f, -leftCornerRadius)
|
||||
canvas.drawPath(invertedLeftCornerPath, paint)
|
||||
canvas.translate(0f, leftCornerRadius)
|
||||
canvas.translate(canvas.width - rightCornerRadius, -rightCornerRadius)
|
||||
canvas.drawPath(invertedRightCornerPath, paint)
|
||||
} else if (!isInSetup) {
|
||||
// backgroundHeight is a value from [0...maxBackgroundHeight], so we can use it as a
|
||||
// proxy to figure out the animation progress of the stash/unstash animation.
|
||||
val progress = backgroundHeight / maxBackgroundHeight
|
||||
|
||||
// At progress 0, we draw the background as the stashed handle.
|
||||
// At progress 1, we draw the background as the full taskbar.
|
||||
val newBackgroundHeight =
|
||||
mapRange(progress, stashedHandleHeight.toFloat(), maxBackgroundHeight)
|
||||
val fullWidth = transientBackgroundBounds.width()
|
||||
val newWidth = mapRange(progress, stashedHandleWidth.toFloat(), fullWidth.toFloat())
|
||||
val halfWidthDelta = (fullWidth - newWidth) / 2f
|
||||
val radius = newBackgroundHeight / 2f
|
||||
val bottomMarginProgress = bottomMargin * ((1f - progress) / 2f)
|
||||
|
||||
// Aligns the bottom with the bottom of the stashed handle.
|
||||
val bottom =
|
||||
canvas.height - bottomMargin +
|
||||
bottomMarginProgress +
|
||||
translationYForSwipe +
|
||||
translationYForStash +
|
||||
-mapRange(1f - progress, 0f, stashedHandleHeight / 2f)
|
||||
|
||||
// Draw shadow.
|
||||
val newShadowAlpha =
|
||||
mapToRange(paint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
|
||||
paint.setShadowLayer(
|
||||
shadowBlur,
|
||||
0f,
|
||||
keyShadowDistance,
|
||||
setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
|
||||
)
|
||||
|
||||
lastDrawnTransientRect.set(
|
||||
transientBackgroundBounds.left + halfWidthDelta,
|
||||
bottom - newBackgroundHeight,
|
||||
transientBackgroundBounds.right - halfWidthDelta,
|
||||
bottom
|
||||
)
|
||||
val horizontalInset = fullWidth * widthInsetPercentage
|
||||
lastDrawnTransientRect.inset(horizontalInset, 0f)
|
||||
|
||||
canvas.drawRoundRect(lastDrawnTransientRect, radius, radius, paint)
|
||||
if (!isTransientTaskbar || transientBackgroundBounds.isEmpty || isAnimatingPinning) {
|
||||
drawPersistentBackground(canvas)
|
||||
}
|
||||
canvas.restore()
|
||||
canvas.save()
|
||||
if (isAnimatingPinning || isTransientTaskbar) {
|
||||
drawTransientBackground(canvas)
|
||||
}
|
||||
canvas.restore()
|
||||
}
|
||||
|
||||
private fun drawPersistentBackground(canvas: Canvas) {
|
||||
if (isAnimatingPinning) {
|
||||
val persistentTaskbarHeight = maxPersistentTaskbarHeight * backgroundProgress
|
||||
canvas.translate(0f, canvas.height - persistentTaskbarHeight)
|
||||
// Draw the background behind taskbar content.
|
||||
canvas.drawRect(0f, 0f, canvas.width.toFloat(), persistentTaskbarHeight, paint)
|
||||
} else {
|
||||
val persistentTaskbarHeight = min(maxPersistentTaskbarHeight, backgroundHeight)
|
||||
canvas.translate(0f, canvas.height - persistentTaskbarHeight)
|
||||
// Draw the background behind taskbar content.
|
||||
canvas.drawRect(0f, 0f, canvas.width.toFloat(), persistentTaskbarHeight, paint)
|
||||
}
|
||||
|
||||
// Draw the inverted rounded corners above the taskbar.
|
||||
canvas.translate(0f, -leftCornerRadius)
|
||||
canvas.drawPath(invertedLeftCornerPath, paint)
|
||||
canvas.translate(0f, leftCornerRadius)
|
||||
canvas.translate(canvas.width - rightCornerRadius, -rightCornerRadius)
|
||||
canvas.drawPath(invertedRightCornerPath, paint)
|
||||
}
|
||||
|
||||
private fun drawTransientBackground(canvas: Canvas) {
|
||||
val res = context.resources
|
||||
val transientTaskbarHeight = maxTransientTaskbarHeight * (1f - backgroundProgress)
|
||||
val heightProgressWhileAnimating =
|
||||
if (isAnimatingPinning) transientTaskbarHeight else backgroundHeight
|
||||
|
||||
var progress = heightProgressWhileAnimating / maxTransientTaskbarHeight
|
||||
progress = Math.round(progress * 100f) / 100f
|
||||
if (isAnimatingPinning) {
|
||||
var scale = transientTaskbarHeight / maxTransientTaskbarHeight
|
||||
scale = Math.round(scale * 100f) / 100f
|
||||
bottomMargin =
|
||||
mapRange(
|
||||
scale,
|
||||
0f,
|
||||
res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin).toFloat()
|
||||
)
|
||||
.toInt()
|
||||
shadowBlur =
|
||||
mapRange(scale, 0f, res.getDimension(R.dimen.transient_taskbar_shadow_blur))
|
||||
keyShadowDistance =
|
||||
mapRange(scale, 0f, res.getDimension(R.dimen.transient_taskbar_key_shadow_distance))
|
||||
} else {
|
||||
bottomMargin = res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin)
|
||||
shadowBlur = res.getDimension(R.dimen.transient_taskbar_shadow_blur)
|
||||
keyShadowDistance = res.getDimension(R.dimen.transient_taskbar_key_shadow_distance)
|
||||
}
|
||||
|
||||
// At progress 0, we draw the background as the stashed handle.
|
||||
// At progress 1, we draw the background as the full taskbar.
|
||||
// Min height capped to max persistent taskbar height for animation
|
||||
val backgroundHeightWhileAnimating =
|
||||
if (isAnimatingPinning) maxPersistentTaskbarHeight else stashedHandleHeight.toFloat()
|
||||
val newBackgroundHeight =
|
||||
mapRange(progress, backgroundHeightWhileAnimating, maxTransientTaskbarHeight)
|
||||
val fullWidth = transientBackgroundBounds.width()
|
||||
|
||||
// .9f is here to restrict min width of the background while animating, so transient
|
||||
// background keeps it pill shape until animation end.
|
||||
val animationWidth =
|
||||
if (DisplayController.isTransientTaskbar(context)) fullWidth.toFloat() * .9f
|
||||
else fullWidth.toFloat()
|
||||
val backgroundWidthWhileAnimating =
|
||||
if (isAnimatingPinning) animationWidth else stashedHandleWidth.toFloat()
|
||||
|
||||
val newWidth = mapRange(progress, backgroundWidthWhileAnimating, fullWidth.toFloat())
|
||||
val halfWidthDelta = (fullWidth - newWidth) / 2f
|
||||
val radius = newBackgroundHeight / 2f
|
||||
val bottomMarginProgress = bottomMargin * ((1f - progress) / 2f)
|
||||
|
||||
// Aligns the bottom with the bottom of the stashed handle.
|
||||
val bottom =
|
||||
canvas.height - bottomMargin +
|
||||
bottomMarginProgress +
|
||||
translationYForSwipe +
|
||||
translationYForStash +
|
||||
-mapRange(
|
||||
1f - progress,
|
||||
0f,
|
||||
if (isAnimatingPinning) 0f else stashedHandleHeight / 2f
|
||||
)
|
||||
|
||||
// Draw shadow.
|
||||
val newShadowAlpha =
|
||||
mapToRange(paint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
|
||||
paint.setShadowLayer(
|
||||
shadowBlur,
|
||||
0f,
|
||||
keyShadowDistance,
|
||||
setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
|
||||
)
|
||||
|
||||
lastDrawnTransientRect.set(
|
||||
transientBackgroundBounds.left + halfWidthDelta,
|
||||
bottom - newBackgroundHeight,
|
||||
transientBackgroundBounds.right - halfWidthDelta,
|
||||
bottom
|
||||
)
|
||||
val horizontalInset = fullWidth * widthInsetPercentage
|
||||
lastDrawnTransientRect.inset(horizontalInset, 0f)
|
||||
|
||||
canvas.drawRoundRect(lastDrawnTransientRect, radius, radius, paint)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -62,7 +62,7 @@ public class TaskbarControllers {
|
||||
public final TaskbarOverlayController taskbarOverlayController;
|
||||
public final TaskbarEduTooltipController taskbarEduTooltipController;
|
||||
public final KeyboardQuickSwitchController keyboardQuickSwitchController;
|
||||
public final TaskbarDividerPopupController taskbarPinningController;
|
||||
public final TaskbarPinningController taskbarPinningController;
|
||||
public final Optional<BubbleControllers> bubbleControllers;
|
||||
|
||||
@Nullable private LoggableTaskbarController[] mControllersToLog = null;
|
||||
@@ -110,7 +110,7 @@ public class TaskbarControllers {
|
||||
TaskbarRecentAppsController taskbarRecentAppsController,
|
||||
TaskbarEduTooltipController taskbarEduTooltipController,
|
||||
KeyboardQuickSwitchController keyboardQuickSwitchController,
|
||||
TaskbarDividerPopupController taskbarPinningController,
|
||||
TaskbarPinningController taskbarPinningController,
|
||||
Optional<BubbleControllers> bubbleControllers) {
|
||||
this.taskbarActivityContext = taskbarActivityContext;
|
||||
this.taskbarDragController = taskbarDragController;
|
||||
@@ -171,7 +171,7 @@ public class TaskbarControllers {
|
||||
taskbarTranslationController.init(this);
|
||||
taskbarEduTooltipController.init(this);
|
||||
keyboardQuickSwitchController.init(this);
|
||||
taskbarPinningController.init(this);
|
||||
taskbarPinningController.init(this, mSharedState);
|
||||
bubbleControllers.ifPresent(controllers -> controllers.init(this));
|
||||
|
||||
mControllersToLog = new LoggableTaskbarController[] {
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.launcher3.taskbar
|
||||
|
||||
import android.view.View
|
||||
import com.android.launcher3.LauncherPrefs
|
||||
import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
|
||||
import com.android.launcher3.taskbar.TaskbarDividerPopupView.Companion.createAndPopulate
|
||||
import java.io.PrintWriter
|
||||
|
||||
/** Controls taskbar pinning through a popup view. */
|
||||
class TaskbarDividerPopupController(private val context: TaskbarActivityContext) :
|
||||
TaskbarControllers.LoggableTaskbarController {
|
||||
|
||||
private lateinit var controllers: TaskbarControllers
|
||||
private val launcherPrefs = LauncherPrefs.get(context)
|
||||
|
||||
fun init(taskbarControllers: TaskbarControllers) {
|
||||
controllers = taskbarControllers
|
||||
}
|
||||
|
||||
fun showPinningView(view: View) {
|
||||
context.isTaskbarWindowFullscreen = true
|
||||
|
||||
view.post {
|
||||
val popupView = createAndPopulate(view, context)
|
||||
popupView.requestFocus()
|
||||
|
||||
popupView.onCloseCallback =
|
||||
callback@{ didPreferenceChange ->
|
||||
context.dragLayer.post { context.onPopupVisibilityChanged(false) }
|
||||
|
||||
if (!didPreferenceChange) {
|
||||
return@callback
|
||||
}
|
||||
|
||||
if (launcherPrefs.get(TASKBAR_PINNING)) {
|
||||
animateTransientToPersistentTaskbar()
|
||||
} else {
|
||||
animatePersistentToTransientTaskbar()
|
||||
}
|
||||
}
|
||||
popupView.changePreference = {
|
||||
launcherPrefs.put(TASKBAR_PINNING, !launcherPrefs.get(TASKBAR_PINNING))
|
||||
}
|
||||
context.onPopupVisibilityChanged(true)
|
||||
popupView.show()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/265436799): provide animation/transition from transient taskbar to persistent one
|
||||
private fun animateTransientToPersistentTaskbar() {}
|
||||
|
||||
// TODO(b/265436799): provide animation/transition from persistent taskbar to transient one
|
||||
private fun animatePersistentToTransientTaskbar() {}
|
||||
|
||||
override fun dumpLogs(prefix: String, pw: PrintWriter) {
|
||||
pw.println(prefix + "TaskbarPinningController:")
|
||||
}
|
||||
}
|
||||
@@ -15,22 +15,29 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ObjectAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.util.AttributeSet
|
||||
import android.util.Property
|
||||
import android.view.Gravity
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Switch
|
||||
import androidx.core.view.postDelayed
|
||||
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.popup.ArrowPopup
|
||||
import com.android.launcher3.popup.RoundedArrowDrawable
|
||||
import com.android.launcher3.util.DisplayController
|
||||
import com.android.launcher3.util.Themes
|
||||
import com.android.launcher3.views.ActivityContext
|
||||
|
||||
/** Popup view with arrow for taskbar pinning */
|
||||
class TaskbarDividerPopupView<T : TaskbarActivityContext>
|
||||
@@ -42,7 +49,8 @@ constructor(
|
||||
) : ArrowPopup<T>(context, attrs, defStyleAttr) {
|
||||
companion object {
|
||||
private const val TAG = "TaskbarDividerPopupView"
|
||||
private const val DIVIDER_POPUP_CLOSING_DELAY = 500L
|
||||
private const val DIVIDER_POPUP_CLOSING_DELAY = 333L
|
||||
private const val DIVIDER_POPUP_CLOSING_ANIMATION_DURATION = 83L
|
||||
|
||||
@JvmStatic
|
||||
fun createAndPopulate(
|
||||
@@ -59,10 +67,11 @@ constructor(
|
||||
return taskMenuViewWithArrow.populateForView(view)
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var dividerView: View
|
||||
|
||||
private val menuWidth =
|
||||
context.resources.getDimensionPixelSize(R.dimen.taskbar_pinning_popup_menu_width)
|
||||
resources.getDimensionPixelSize(R.dimen.taskbar_pinning_popup_menu_width)
|
||||
private val popupCornerRadius = Themes.getDialogCornerRadius(context)
|
||||
private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width)
|
||||
private val arrowHeight = resources.getDimension(R.dimen.popup_arrow_height)
|
||||
@@ -70,16 +79,12 @@ constructor(
|
||||
|
||||
private var alwaysShowTaskbarOn = !DisplayController.isTransientTaskbar(context)
|
||||
private var didPreferenceChange = false
|
||||
private var verticalOffsetForPopupView =
|
||||
resources.getDimensionPixelSize(R.dimen.taskbar_pinning_popup_menu_vertical_margin)
|
||||
|
||||
/** Callback invoked when the pinning popup view is closing. */
|
||||
var onCloseCallback: (preferenceChanged: Boolean) -> Unit = {}
|
||||
|
||||
/**
|
||||
* Callback invoked when the user preference changes in popup view. Preference change will be
|
||||
* based upon current value stored in [LauncherPrefs] for `TASKBAR_PINNING`
|
||||
*/
|
||||
var changePreference: () -> Unit = {}
|
||||
|
||||
init {
|
||||
// This synchronizes the arrow and menu to open at the same time
|
||||
mOpenChildFadeStartDelay = mOpenFadeStartDelay
|
||||
@@ -99,11 +104,22 @@ constructor(
|
||||
super.onFinishInflate()
|
||||
val taskbarSwitchOption = requireViewById<LinearLayout>(R.id.taskbar_switch_option)
|
||||
val alwaysShowTaskbarSwitch = requireViewById<Switch>(R.id.taskbar_pinning_switch)
|
||||
val taskbarVisibilityIcon = requireViewById<View>(R.id.taskbar_pinning_visibility_icon)
|
||||
alwaysShowTaskbarSwitch.isChecked = alwaysShowTaskbarOn
|
||||
taskbarSwitchOption.setOnClickListener {
|
||||
alwaysShowTaskbarSwitch.isClickable = true
|
||||
alwaysShowTaskbarSwitch.isChecked = !alwaysShowTaskbarOn
|
||||
onClickAlwaysShowTaskbarSwitchOption()
|
||||
if (ActivityContext.lookupContext<TaskbarActivityContext>(context).isGestureNav) {
|
||||
taskbarSwitchOption.setOnClickListener {
|
||||
alwaysShowTaskbarSwitch.isClickable = true
|
||||
alwaysShowTaskbarSwitch.isChecked = !alwaysShowTaskbarOn
|
||||
onClickAlwaysShowTaskbarSwitchOption()
|
||||
}
|
||||
} else {
|
||||
alwaysShowTaskbarSwitch.isEnabled = false
|
||||
}
|
||||
|
||||
if (!alwaysShowTaskbarSwitch.isEnabled) {
|
||||
taskbarVisibilityIcon.background.setTint(
|
||||
resources.getColor(android.R.color.system_neutral2_500, context.theme)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,15 +192,80 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun closeComplete() {
|
||||
override fun getExtraVerticalOffset(): Int {
|
||||
return (mActivityContext.deviceProfile.taskbarHeight -
|
||||
mActivityContext.deviceProfile.taskbarIconSize) / 2 + verticalOffsetForPopupView
|
||||
}
|
||||
|
||||
override fun animateClose() {
|
||||
if (!mIsOpen) {
|
||||
return
|
||||
}
|
||||
if (mOpenCloseAnimator != null) {
|
||||
mOpenCloseAnimator.cancel()
|
||||
}
|
||||
mIsOpen = false
|
||||
|
||||
mOpenCloseAnimator = getCloseAnimator()
|
||||
|
||||
mOpenCloseAnimator.addListener(
|
||||
object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
mOpenCloseAnimator = null
|
||||
if (mDeferContainerRemoval) {
|
||||
setVisibility(INVISIBLE)
|
||||
} else {
|
||||
closeComplete()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
onCloseCallback(didPreferenceChange)
|
||||
super.closeComplete()
|
||||
onCloseCallback = {}
|
||||
mOpenCloseAnimator.start()
|
||||
}
|
||||
|
||||
private fun getCloseAnimator(): AnimatorSet {
|
||||
val alphaValues = floatArrayOf(1f, 0f)
|
||||
val translateYValue =
|
||||
if (!alwaysShowTaskbarOn) verticalOffsetForPopupView else -verticalOffsetForPopupView
|
||||
val alpha = getAnimatorOfFloat(this, ALPHA, *alphaValues)
|
||||
val arrowAlpha = getAnimatorOfFloat(mArrow, ALPHA, *alphaValues)
|
||||
val translateY =
|
||||
ObjectAnimator.ofFloat(
|
||||
this,
|
||||
TRANSLATION_Y,
|
||||
*floatArrayOf(this.translationY, this.translationY + translateYValue)
|
||||
)
|
||||
val arrowTranslateY =
|
||||
ObjectAnimator.ofFloat(
|
||||
mArrow,
|
||||
TRANSLATION_Y,
|
||||
*floatArrayOf(mArrow.translationY, mArrow.translationY + translateYValue)
|
||||
)
|
||||
val animatorSet = AnimatorSet()
|
||||
animatorSet.playTogether(alpha, arrowAlpha, translateY, arrowTranslateY)
|
||||
return animatorSet
|
||||
}
|
||||
|
||||
private fun getAnimatorOfFloat(
|
||||
view: View,
|
||||
property: Property<View, Float>,
|
||||
vararg values: Float
|
||||
): Animator {
|
||||
val animator: Animator = ObjectAnimator.ofFloat(view, property, *values)
|
||||
animator.setDuration(DIVIDER_POPUP_CLOSING_ANIMATION_DURATION)
|
||||
animator.interpolator = EMPHASIZED_ACCELERATE
|
||||
return animator
|
||||
}
|
||||
|
||||
private fun onClickAlwaysShowTaskbarSwitchOption() {
|
||||
didPreferenceChange = true
|
||||
changePreference()
|
||||
// Allow switch animation to finish and then close the popup.
|
||||
postDelayed(DIVIDER_POPUP_CLOSING_DELAY) { close(true) }
|
||||
postDelayed(DIVIDER_POPUP_CLOSING_DELAY) {
|
||||
if (isOpen) {
|
||||
close(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APP
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION;
|
||||
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS;
|
||||
import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
@@ -64,6 +66,7 @@ import com.android.launcher3.dragndrop.DragOptions;
|
||||
import com.android.launcher3.dragndrop.DragView;
|
||||
import com.android.launcher3.dragndrop.DraggableView;
|
||||
import com.android.launcher3.graphics.DragPreviewProvider;
|
||||
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
|
||||
import com.android.launcher3.logging.StatsLogManager;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
@@ -259,6 +262,8 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source,
|
||||
ItemInfo dragInfo, Rect dragRegion, float initialDragViewScale,
|
||||
float dragViewScaleOnDrop, DragOptions options) {
|
||||
mActivity.hideKeyboard();
|
||||
|
||||
mOptions = options;
|
||||
|
||||
mRegistrationX = mMotionDown.x - dragLayerX;
|
||||
@@ -624,7 +629,9 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
|
||||
if (tag instanceof ItemInfo) {
|
||||
ItemInfo item = (ItemInfo) tag;
|
||||
if (item.container == CONTAINER_ALL_APPS || item.container == CONTAINER_PREDICTION) {
|
||||
if (item.container == CONTAINER_ALL_APPS
|
||||
|| item.container == CONTAINER_PREDICTION
|
||||
|| isInSearchResultContainer(item)) {
|
||||
if (mDisallowGlobalDrag) {
|
||||
// We're dragging in taskbarAllApps, we don't have folders or shortcuts
|
||||
return iconView;
|
||||
@@ -646,6 +653,13 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
return iconView;
|
||||
}
|
||||
|
||||
private static boolean isInSearchResultContainer(ItemInfo item) {
|
||||
ContainerInfo containerInfo = item.getContainerInfo();
|
||||
return containerInfo.getContainerCase() == EXTENDED_CONTAINERS
|
||||
&& containerInfo.getExtendedContainers().getContainerCase()
|
||||
== DEVICE_SEARCH_RESULT_CONTAINER;
|
||||
}
|
||||
|
||||
private void setupReturnDragAnimator(float fromX, float fromY, View originalView,
|
||||
TaskbarReturnPropertiesListener animListener) {
|
||||
// Finish any pending return animation before starting a new return
|
||||
|
||||
@@ -18,6 +18,8 @@ package com.android.launcher3.taskbar;
|
||||
import static android.view.KeyEvent.ACTION_UP;
|
||||
import static android.view.KeyEvent.KEYCODE_BACK;
|
||||
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.RectF;
|
||||
@@ -73,6 +75,8 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
private SafeCloseable mViewCaptureCloseable;
|
||||
|
||||
private float mTaskbarBackgroundOffset;
|
||||
private float mTaskbarBackgroundProgress;
|
||||
private boolean mIsAnimatingTaskbarPinning = false;
|
||||
|
||||
private final MultiPropertyFactory<TaskbarDragLayer> mTaskbarBackgroundAlpha;
|
||||
|
||||
@@ -124,7 +128,7 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
}
|
||||
|
||||
protected void onDestroy() {
|
||||
onDestroy(!TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW);
|
||||
onDestroy(!ENABLE_TASKBAR_NAVBAR_UNIFICATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -162,10 +166,19 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
|
||||
* (1f - mTaskbarBackgroundOffset);
|
||||
mBackgroundRenderer.setBackgroundHeight(backgroundHeight);
|
||||
mBackgroundRenderer.setBackgroundProgress(mTaskbarBackgroundProgress);
|
||||
mBackgroundRenderer.draw(canvas);
|
||||
super.dispatchDraw(canvas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets animation boolean when taskbar pinning animation starts or stops.
|
||||
*/
|
||||
public void setAnimatingTaskbarPinning(boolean animatingTaskbarPinning) {
|
||||
mIsAnimatingTaskbarPinning = animatingTaskbarPinning;
|
||||
mBackgroundRenderer.setAnimatingPinning(mIsAnimatingTaskbarPinning);
|
||||
}
|
||||
|
||||
protected MultiProperty getBackgroundRendererAlpha() {
|
||||
return mTaskbarBackgroundAlpha.get(INDEX_ALL_OTHER_STATES);
|
||||
}
|
||||
@@ -174,6 +187,15 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
return mTaskbarBackgroundAlpha.get(INDEX_STASH_ANIM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value for taskbar background switching between persistent and transient backgrounds.
|
||||
* @param progress 0 is transient background, 1 is persistent background.
|
||||
*/
|
||||
protected void setTaskbarBackgroundProgress(float progress) {
|
||||
mTaskbarBackgroundProgress = progress;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the translation of the background color behind all the Taskbar contents.
|
||||
* @param offset 0 is fully onscreen, 1 is fully offscreen.
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT;
|
||||
import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
@@ -24,6 +27,7 @@ import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.util.DimensionUtils;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
|
||||
@@ -58,6 +62,9 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
// changes the inset visibility.
|
||||
private final AnimatedFloat mTaskbarAlpha = new AnimatedFloat(this::updateTaskbarAlpha);
|
||||
|
||||
private final AnimatedFloat mTaskbarBackgroundProgress = new AnimatedFloat(
|
||||
this::updateTaskbarBackgroundProgress);
|
||||
|
||||
// Initialized in init.
|
||||
private TaskbarControllers mControllers;
|
||||
private TaskbarStashViaTouchController mTaskbarStashViaTouchController;
|
||||
@@ -83,6 +90,10 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
mOnBackgroundNavButtonColorIntensity = mControllers.navbarButtonsViewController
|
||||
.getOnTaskbarBackgroundNavButtonColorOverride();
|
||||
|
||||
mTaskbarBackgroundProgress.updateValue(DisplayController.isTransientTaskbar(mActivity)
|
||||
? PINNING_TRANSIENT
|
||||
: PINNING_PERSISTENT);
|
||||
|
||||
mBgTaskbar.value = 1;
|
||||
mKeyguardBgTaskbar.value = 1;
|
||||
mNotificationShadeBgTaskbar.value = 1;
|
||||
@@ -138,6 +149,11 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
return mBgOffset;
|
||||
}
|
||||
|
||||
// AnimatedFloat is for animating between pinned and transient taskbar
|
||||
public AnimatedFloat getTaskbarBackgroundProgress() {
|
||||
return mTaskbarBackgroundProgress;
|
||||
}
|
||||
|
||||
public AnimatedFloat getTaskbarAlpha() {
|
||||
return mTaskbarAlpha;
|
||||
}
|
||||
@@ -180,10 +196,13 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
|
||||
private void updateBackgroundOffset() {
|
||||
mTaskbarDragLayer.setTaskbarBackgroundOffset(mBgOffset.value);
|
||||
|
||||
updateOnBackgroundNavButtonColorIntensity();
|
||||
}
|
||||
|
||||
private void updateTaskbarBackgroundProgress() {
|
||||
mTaskbarDragLayer.setTaskbarBackgroundProgress(mTaskbarBackgroundProgress.value);
|
||||
}
|
||||
|
||||
private void updateTaskbarAlpha() {
|
||||
mTaskbarDragLayer.setAlpha(mTaskbarAlpha.value);
|
||||
}
|
||||
|
||||
@@ -29,8 +29,10 @@ import androidx.core.view.updateLayoutParams
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.Utilities
|
||||
import com.android.launcher3.config.FeatureFlags.enableTaskbarPinningEdu
|
||||
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
|
||||
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
|
||||
import com.android.launcher3.taskbar.TaskbarManager.isPhoneMode
|
||||
import com.android.launcher3.util.DisplayController
|
||||
import com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP
|
||||
import com.android.quickstep.util.LottieAnimationColorUtils
|
||||
@@ -40,16 +42,19 @@ import java.io.PrintWriter
|
||||
const val TOOLTIP_STEP_SWIPE = 0
|
||||
/** Second EDU step for explaining Taskbar functionality when unstashed. */
|
||||
const val TOOLTIP_STEP_FEATURES = 1
|
||||
/** Third EDU step for explaining Taskbar pinning. */
|
||||
const val TOOLTIP_STEP_PINNING = 2
|
||||
|
||||
/**
|
||||
* EDU is completed.
|
||||
*
|
||||
* This value should match the maximum count for [TASKBAR_EDU_TOOLTIP_STEP].
|
||||
*/
|
||||
const val TOOLTIP_STEP_NONE = 2
|
||||
const val TOOLTIP_STEP_NONE = 3
|
||||
|
||||
/** Current step in the tooltip EDU flow. */
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@IntDef(TOOLTIP_STEP_SWIPE, TOOLTIP_STEP_FEATURES, TOOLTIP_STEP_NONE)
|
||||
@IntDef(TOOLTIP_STEP_SWIPE, TOOLTIP_STEP_FEATURES, TOOLTIP_STEP_PINNING, TOOLTIP_STEP_NONE)
|
||||
annotation class TaskbarEduTooltipStep
|
||||
|
||||
/** Controls stepping through the Taskbar tooltip EDU. */
|
||||
@@ -57,7 +62,7 @@ class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
|
||||
LoggableTaskbarController {
|
||||
|
||||
private val isTooltipEnabled: Boolean
|
||||
get() = !Utilities.isRunningInTestHarness()
|
||||
get() = !Utilities.isRunningInTestHarness() && !isPhoneMode(activityContext.deviceProfile)
|
||||
private val isOpen: Boolean
|
||||
get() = tooltip?.isOpen ?: false
|
||||
val isBeforeTooltipFeaturesStep: Boolean
|
||||
@@ -67,11 +72,10 @@ class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
|
||||
@TaskbarEduTooltipStep
|
||||
var tooltipStep: Int
|
||||
get() {
|
||||
return activityContext.onboardingPrefs?.getCount(TASKBAR_EDU_TOOLTIP_STEP)
|
||||
?: TOOLTIP_STEP_NONE
|
||||
return TASKBAR_EDU_TOOLTIP_STEP.get(activityContext)
|
||||
}
|
||||
private set(step) {
|
||||
activityContext.onboardingPrefs?.setEventCount(step, TASKBAR_EDU_TOOLTIP_STEP)
|
||||
TASKBAR_EDU_TOOLTIP_STEP.set(step, activityContext)
|
||||
}
|
||||
|
||||
private var tooltip: TaskbarEduTooltip? = null
|
||||
@@ -114,19 +118,19 @@ class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
|
||||
tooltip?.run {
|
||||
val splitscreenAnim = requireViewById<LottieAnimationView>(R.id.splitscreen_animation)
|
||||
val suggestionsAnim = requireViewById<LottieAnimationView>(R.id.suggestions_animation)
|
||||
val settingsAnim = requireViewById<LottieAnimationView>(R.id.settings_animation)
|
||||
val settingsEdu = requireViewById<View>(R.id.settings_edu)
|
||||
val pinningAnim = requireViewById<LottieAnimationView>(R.id.pinning_animation)
|
||||
val pinningEdu = requireViewById<View>(R.id.pinning_edu)
|
||||
splitscreenAnim.supportLightTheme()
|
||||
suggestionsAnim.supportLightTheme()
|
||||
settingsAnim.supportLightTheme()
|
||||
pinningAnim.supportLightTheme()
|
||||
if (DisplayController.isTransientTaskbar(activityContext)) {
|
||||
splitscreenAnim.setAnimation(R.raw.taskbar_edu_splitscreen_transient)
|
||||
suggestionsAnim.setAnimation(R.raw.taskbar_edu_suggestions_transient)
|
||||
settingsEdu.visibility = GONE
|
||||
pinningEdu.visibility = if (enableTaskbarPinningEdu()) VISIBLE else GONE
|
||||
} else {
|
||||
splitscreenAnim.setAnimation(R.raw.taskbar_edu_splitscreen_persistent)
|
||||
suggestionsAnim.setAnimation(R.raw.taskbar_edu_suggestions_persistent)
|
||||
settingsEdu.visibility = VISIBLE
|
||||
pinningEdu.visibility = GONE
|
||||
}
|
||||
|
||||
// Set up layout parameters.
|
||||
@@ -135,13 +139,16 @@ class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
|
||||
if (DisplayController.isTransientTaskbar(activityContext)) {
|
||||
width =
|
||||
resources.getDimensionPixelSize(
|
||||
R.dimen.taskbar_edu_features_tooltip_width_transient
|
||||
if (enableTaskbarPinningEdu())
|
||||
R.dimen.taskbar_edu_features_tooltip_width_with_three_features
|
||||
else R.dimen.taskbar_edu_features_tooltip_width_with_two_features
|
||||
)
|
||||
|
||||
bottomMargin += activityContext.deviceProfile.taskbarHeight
|
||||
} else {
|
||||
width =
|
||||
resources.getDimensionPixelSize(
|
||||
R.dimen.taskbar_edu_features_tooltip_width_persistent
|
||||
R.dimen.taskbar_edu_features_tooltip_width_with_two_features
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -251,5 +258,5 @@ private fun LottieAnimationView.supportLightTheme() {
|
||||
return
|
||||
}
|
||||
|
||||
LottieAnimationColorUtils.updateColors(this, DARK_TO_LIGHT_COLORS, context.theme)
|
||||
LottieAnimationColorUtils.updateToColorResources(this, DARK_TO_LIGHT_COLORS, context.theme)
|
||||
}
|
||||
|
||||
+3
-4
@@ -22,7 +22,7 @@ import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBIL
|
||||
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
|
||||
|
||||
import static com.android.launcher3.taskbar.NavbarButtonsViewController.ALPHA_INDEX_IMMERSIVE_MODE;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IMMERSIVE_MODE;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
@@ -84,7 +84,7 @@ public class TaskbarForceVisibleImmersiveController implements TouchController {
|
||||
|
||||
/** Update values tracked via sysui flags. */
|
||||
public void updateSysuiFlags(int sysuiFlags) {
|
||||
mIsImmersiveMode = (sysuiFlags & SYSUI_STATE_IMMERSIVE_MODE) != 0;
|
||||
mIsImmersiveMode = (sysuiFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) == 0;
|
||||
if (mContext.isNavBarForceVisible()) {
|
||||
if (mIsImmersiveMode) {
|
||||
startIconDimming();
|
||||
@@ -158,8 +158,7 @@ public class TaskbarForceVisibleImmersiveController implements TouchController {
|
||||
|
||||
@Override
|
||||
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
|
||||
if (!isNavbarShownInImmersiveMode()
|
||||
|| mControllers.taskbarStashController.supportsManualStashing()) {
|
||||
if (!isNavbarShownInImmersiveMode()) {
|
||||
return false;
|
||||
}
|
||||
return onControllerTouchEvent(ev);
|
||||
|
||||
@@ -35,8 +35,6 @@ import android.view.ContextThemeWrapper;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
@@ -51,8 +49,7 @@ import com.android.launcher3.views.ArrowTipView;
|
||||
*/
|
||||
public class TaskbarHoverToolTipController implements View.OnHoverListener {
|
||||
|
||||
@VisibleForTesting protected static final int HOVER_TOOL_TIP_REVEAL_START_DELAY = 400;
|
||||
private static final int HOVER_TOOL_TIP_REVEAL_DURATION = 300;
|
||||
private static final int HOVER_TOOL_TIP_REVEAL_DURATION = 250;
|
||||
private static final int HOVER_TOOL_TIP_EXIT_DURATION = 150;
|
||||
|
||||
private final Handler mHoverToolTipHandler = new Handler(Looper.getMainLooper());
|
||||
@@ -84,6 +81,12 @@ public class TaskbarHoverToolTipController implements View.OnHoverListener {
|
||||
R.style.ArrowTipTaskbarStyle);
|
||||
mHoverToolTipView = new ArrowTipView(arrowContextWrapper, /* isPointingUp = */ false,
|
||||
R.layout.arrow_toast);
|
||||
int verticalPadding = arrowContextWrapper.getResources().getDimensionPixelSize(
|
||||
R.dimen.taskbar_tooltip_vertical_padding);
|
||||
int horizontalPadding = arrowContextWrapper.getResources().getDimensionPixelSize(
|
||||
R.dimen.taskbar_tooltip_horizontal_padding);
|
||||
mHoverToolTipView.findViewById(R.id.text).setPadding(horizontalPadding, verticalPadding,
|
||||
horizontalPadding, verticalPadding);
|
||||
|
||||
AnimatorSet hoverCloseAnimator = new AnimatorSet();
|
||||
ObjectAnimator textCloseAnimator = ObjectAnimator.ofInt(mHoverToolTipView, TEXT_ALPHA, 0);
|
||||
@@ -101,17 +104,18 @@ public class TaskbarHoverToolTipController implements View.OnHoverListener {
|
||||
mHoverToolTipView.setCustomCloseAnimation(hoverCloseAnimator);
|
||||
|
||||
AnimatorSet hoverOpenAnimator = new AnimatorSet();
|
||||
ObjectAnimator textOpenAnimator = ObjectAnimator.ofInt(mHoverToolTipView, TEXT_ALPHA, 255);
|
||||
textOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.33f, 1f));
|
||||
ObjectAnimator scaleOpenAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, SCALE_Y, 1f);
|
||||
ObjectAnimator textOpenAnimator =
|
||||
ObjectAnimator.ofInt(mHoverToolTipView, TEXT_ALPHA, 0, 255);
|
||||
textOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.15f, 0.75f));
|
||||
ObjectAnimator scaleOpenAnimator =
|
||||
ObjectAnimator.ofFloat(mHoverToolTipView, SCALE_Y, 0f, 1f);
|
||||
scaleOpenAnimator.setInterpolator(Interpolators.EMPHASIZED);
|
||||
ObjectAnimator alphaOpenAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, ALPHA, 1f);
|
||||
alphaOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.1f, 0.33f));
|
||||
ObjectAnimator alphaOpenAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, ALPHA, 0f, 1f);
|
||||
alphaOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0f, 0.33f));
|
||||
hoverOpenAnimator.playTogether(
|
||||
scaleOpenAnimator,
|
||||
textOpenAnimator,
|
||||
alphaOpenAnimator);
|
||||
hoverOpenAnimator.setStartDelay(HOVER_TOOL_TIP_REVEAL_START_DELAY);
|
||||
hoverOpenAnimator.setDuration(HOVER_TOOL_TIP_REVEAL_DURATION);
|
||||
mHoverToolTipView.setCustomOpenAnimation(hoverOpenAnimator);
|
||||
|
||||
@@ -120,8 +124,6 @@ public class TaskbarHoverToolTipController implements View.OnHoverListener {
|
||||
mHoverToolTipView.setPivotY(bottom);
|
||||
mHoverToolTipView.setY(mTaskbarView.getTop() - (bottom - top));
|
||||
});
|
||||
mHoverToolTipView.setScaleY(0f);
|
||||
mHoverToolTipView.setAlpha(0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -147,8 +149,7 @@ public class TaskbarHoverToolTipController implements View.OnHoverListener {
|
||||
}
|
||||
|
||||
private void startRevealHoverToolTip() {
|
||||
mHoverToolTipHandler.postDelayed(mRevealHoverToolTipRunnable,
|
||||
HOVER_TOOL_TIP_REVEAL_START_DELAY);
|
||||
mHoverToolTipHandler.post(mRevealHoverToolTipRunnable);
|
||||
}
|
||||
|
||||
private void revealHoverToolTip() {
|
||||
@@ -158,14 +159,12 @@ public class TaskbarHoverToolTipController implements View.OnHoverListener {
|
||||
if (mHoverView instanceof FolderIcon && !((FolderIcon) mHoverView).getIconVisible()) {
|
||||
return;
|
||||
}
|
||||
mActivity.setTaskbarWindowFullscreen(true);
|
||||
Rect iconViewBounds = Utilities.getViewBounds(mHoverView);
|
||||
mHoverToolTipView.showAtLocation(mToolTipText, iconViewBounds.centerX(),
|
||||
mTaskbarView.getTop(), /* shouldAutoClose= */ false);
|
||||
}
|
||||
|
||||
private void startHideHoverToolTip() {
|
||||
mHoverToolTipHandler.removeCallbacks(mRevealHoverToolTipRunnable);
|
||||
int accessibilityHideTimeout = AccessibilityManagerCompat.getRecommendedTimeoutMillis(
|
||||
mActivity, /* originalTimeout= */ 0, FLAG_CONTENT_TEXT);
|
||||
mHoverToolTipHandler.postDelayed(mHideHoverToolTipRunnable, accessibilityHideTimeout);
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar
|
||||
|
||||
import android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR
|
||||
import android.graphics.Insets
|
||||
import android.graphics.Region
|
||||
import android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import android.view.Gravity
|
||||
@@ -41,6 +41,8 @@ import com.android.internal.policy.GestureNavigationSettingsObserver
|
||||
import com.android.launcher3.DeviceProfile
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.anim.AlphaUpdateListener
|
||||
import com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION
|
||||
import com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate
|
||||
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
|
||||
import com.android.launcher3.util.DisplayController
|
||||
import java.io.PrintWriter
|
||||
@@ -97,7 +99,14 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
0
|
||||
}
|
||||
|
||||
windowLayoutParams.providedInsets = getProvidedInsets(insetsRoundedCornerFlag)
|
||||
windowLayoutParams.providedInsets =
|
||||
if (enableTaskbarNoRecreate()) {
|
||||
getProvidedInsets(controllers.sharedState!!.insetsFrameProviders!!,
|
||||
insetsRoundedCornerFlag)
|
||||
} else {
|
||||
getProvidedInsets(insetsRoundedCornerFlag)
|
||||
}
|
||||
|
||||
if (!context.isGestureNav) {
|
||||
if (windowLayoutParams.paramsForRotation != null) {
|
||||
for (layoutParams in windowLayoutParams.paramsForRotation) {
|
||||
@@ -153,6 +162,26 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
context.notifyUpdateLayoutParams()
|
||||
}
|
||||
|
||||
/**
|
||||
* This is for when ENABLE_TASKBAR_NO_RECREATION is enabled. We generate one instance of
|
||||
* providedInsets and use it across the entire lifecycle of TaskbarManager. The only thing
|
||||
* we need to reset is nav bar flags based on insetsRoundedCornerFlag.
|
||||
*/
|
||||
private fun getProvidedInsets(providedInsets: Array<InsetsFrameProvider>,
|
||||
insetsRoundedCornerFlag: Int): Array<InsetsFrameProvider> {
|
||||
val navBarsFlag =
|
||||
(if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
|
||||
for (provider in providedInsets) {
|
||||
if (provider.type == navigationBars()) {
|
||||
provider.setFlags(
|
||||
navBarsFlag,
|
||||
FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER
|
||||
)
|
||||
}
|
||||
}
|
||||
return providedInsets
|
||||
}
|
||||
|
||||
/**
|
||||
* The inset types and number of insets provided have to match for both gesture nav and button
|
||||
* nav. The values and the order of the elements in array are allowed to differ.
|
||||
@@ -197,7 +226,6 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
provider.insetsSize = Insets.of(0, 0, rightIndexInset, 0)
|
||||
}
|
||||
|
||||
|
||||
// When in gesture nav, report the stashed height to the IME, to allow hiding the
|
||||
// IME navigation bar.
|
||||
val imeInsetsSize = if (ENABLE_HIDE_IME_CAPTION_BAR && context.isGestureNav) {
|
||||
@@ -208,6 +236,12 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
val imeInsetsSizeOverride =
|
||||
arrayOf(
|
||||
InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
|
||||
InsetsFrameProvider.InsetsSizeOverride(TYPE_VOICE_INTERACTION,
|
||||
// No-op override to keep the size and types in sync with the
|
||||
// override below (insetsSizeOverrides must have the same length and
|
||||
// types after the window is added according to
|
||||
// WindowManagerService#relayoutWindow)
|
||||
provider.insetsSize)
|
||||
)
|
||||
// Use 0 tappableElement insets for the VoiceInteractionWindow when gesture nav is enabled.
|
||||
val visInsetsSizeForTappableElement =
|
||||
@@ -216,12 +250,11 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
val insetsSizeOverrideForTappableElement =
|
||||
arrayOf(
|
||||
InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
|
||||
InsetsFrameProvider.InsetsSizeOverride(
|
||||
TYPE_VOICE_INTERACTION,
|
||||
InsetsFrameProvider.InsetsSizeOverride(TYPE_VOICE_INTERACTION,
|
||||
visInsetsSizeForTappableElement
|
||||
),
|
||||
)
|
||||
if ((context.isGestureNav || TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
|
||||
if ((context.isGestureNav || ENABLE_TASKBAR_NAVBAR_UNIFICATION)
|
||||
&& provider.type == tappableElement()) {
|
||||
provider.insetsSizeOverrides = insetsSizeOverrideForTappableElement
|
||||
} else if (provider.type != systemGestures()) {
|
||||
|
||||
@@ -17,7 +17,9 @@ package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.app.animation.Interpolators.EMPHASIZED;
|
||||
import static com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED;
|
||||
import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
|
||||
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
|
||||
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_OVERVIEW;
|
||||
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
|
||||
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
|
||||
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
|
||||
@@ -44,6 +46,7 @@ import com.android.launcher3.QuickstepTransitionManager;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.statemanager.StateManager;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
|
||||
@@ -66,13 +69,13 @@ public class TaskbarLauncherStateController {
|
||||
private static final String TAG = TaskbarLauncherStateController.class.getSimpleName();
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
/** Launcher activity is resumed and focused. */
|
||||
public static final int FLAG_RESUMED = 1 << 0;
|
||||
/** Launcher activity is visible and focused. */
|
||||
public static final int FLAG_VISIBLE = 1 << 0;
|
||||
|
||||
/**
|
||||
* A external transition / animation is running that will result in FLAG_RESUMED being set.
|
||||
* A external transition / animation is running that will result in FLAG_VISIBLE being set.
|
||||
**/
|
||||
public static final int FLAG_TRANSITION_TO_RESUMED = 1 << 1;
|
||||
public static final int FLAG_TRANSITION_TO_VISIBLE = 1 << 1;
|
||||
|
||||
/**
|
||||
* Set while the launcher state machine is performing a state transition, see {@link
|
||||
@@ -116,7 +119,7 @@ public class TaskbarLauncherStateController {
|
||||
*/
|
||||
private static final int FLAG_TASKBAR_HIDDEN = 1 << 6;
|
||||
|
||||
private static final int FLAGS_LAUNCHER_ACTIVE = FLAG_RESUMED | FLAG_TRANSITION_TO_RESUMED;
|
||||
private static final int FLAGS_LAUNCHER_ACTIVE = FLAG_VISIBLE | FLAG_TRANSITION_TO_VISIBLE;
|
||||
/** Equivalent to an int with all 1s for binary operation purposes */
|
||||
private static final int FLAGS_ALL = ~0;
|
||||
|
||||
@@ -202,19 +205,32 @@ public class TaskbarLauncherStateController {
|
||||
public void onStateTransitionComplete(LauncherState finalState) {
|
||||
mLauncherState = finalState;
|
||||
updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, false);
|
||||
// TODO(b/279514548) Cleans up bad state that can occur when user interacts with
|
||||
// taskbar on top of transparent activity.
|
||||
if (finalState == LauncherState.NORMAL && mLauncher.hasBeenResumed()) {
|
||||
updateStateForFlag(FLAG_RESUMED, true);
|
||||
}
|
||||
applyState();
|
||||
boolean disallowLongClick = finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
|
||||
boolean disallowLongClick =
|
||||
FeatureFlags.enableSplitContextually()
|
||||
? mLauncher.isSplitSelectionEnabled()
|
||||
: finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
|
||||
com.android.launcher3.taskbar.Utilities.setOverviewDragState(
|
||||
mControllers, finalState.disallowTaskbarGlobalDrag(),
|
||||
disallowLongClick, finalState.allowTaskbarInitialSplitSelection());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for when launcher state transition completes after user swipes to home.
|
||||
* @param finalState The final state of the transition.
|
||||
*/
|
||||
public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) {
|
||||
// TODO(b/279514548) Cleans up bad state that can occur when user interacts with
|
||||
// taskbar on top of transparent activity.
|
||||
if (!FeatureFlags.enableHomeTransitionListener()
|
||||
&& (finalState == LauncherState.NORMAL)
|
||||
&& mLauncher.hasBeenResumed()) {
|
||||
updateStateForFlag(FLAG_VISIBLE, true);
|
||||
applyState();
|
||||
}
|
||||
}
|
||||
|
||||
/** Initializes the controller instance, and applies the initial state immediately. */
|
||||
public void init(TaskbarControllers controllers, QuickstepLauncher launcher,
|
||||
int sysuiStateFlags) {
|
||||
@@ -277,7 +293,7 @@ public class TaskbarLauncherStateController {
|
||||
}
|
||||
stashController.updateStateForFlag(FLAG_IN_APP, false);
|
||||
|
||||
updateStateForFlag(FLAG_TRANSITION_TO_RESUMED, true);
|
||||
updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, true);
|
||||
animatorSet.play(stashController.createApplyStateAnimator(duration));
|
||||
animatorSet.play(applyState(duration, false));
|
||||
|
||||
@@ -416,6 +432,9 @@ public class TaskbarLauncherStateController {
|
||||
controllers.bubbleStashController.setBubblesShowingOnOverview(onOverview);
|
||||
});
|
||||
|
||||
mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_OVERVIEW,
|
||||
mLauncherState == LauncherState.OVERVIEW);
|
||||
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
|
||||
if (hasAnyFlag(changedFlags, FLAG_LAUNCHER_IN_STATE_TRANSITION)) {
|
||||
@@ -425,7 +444,7 @@ public class TaskbarLauncherStateController {
|
||||
if (launcherTransitionCompleted
|
||||
&& mLauncherState == LauncherState.QUICK_SWITCH_FROM_HOME) {
|
||||
// We're about to be paused, set immediately to ensure seamless handoff.
|
||||
updateStateForFlag(FLAG_RESUMED, false);
|
||||
updateStateForFlag(FLAG_VISIBLE, false);
|
||||
applyState(0 /* duration */);
|
||||
}
|
||||
if (mLauncherState == LauncherState.NORMAL) {
|
||||
@@ -715,6 +734,7 @@ public class TaskbarLauncherStateController {
|
||||
}
|
||||
mIconAlphaForHome.setValue(alpha);
|
||||
boolean hotseatVisible = alpha == 0
|
||||
|| isPhoneMode(mLauncher.getDeviceProfile())
|
||||
|| (!mControllers.uiController.isHotseatIconOnTopWhenAligned()
|
||||
&& mIconAlignment.value > 0);
|
||||
/*
|
||||
@@ -751,10 +771,10 @@ public class TaskbarLauncherStateController {
|
||||
mTaskBarRecentsAnimationListener = null;
|
||||
((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null);
|
||||
|
||||
// Update the resumed state immediately to ensure a seamless handoff
|
||||
boolean launcherResumed = !finishedToApp;
|
||||
updateStateForFlag(FLAG_TRANSITION_TO_RESUMED, false);
|
||||
updateStateForFlag(FLAG_RESUMED, launcherResumed);
|
||||
// Update the visible state immediately to ensure a seamless handoff
|
||||
boolean launcherVisible = !finishedToApp;
|
||||
updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, false);
|
||||
updateStateForFlag(FLAG_VISIBLE, launcherVisible);
|
||||
applyState();
|
||||
|
||||
TaskbarStashController controller = mControllers.taskbarStashController;
|
||||
@@ -768,8 +788,8 @@ public class TaskbarLauncherStateController {
|
||||
|
||||
private static String getStateString(int flags) {
|
||||
StringJoiner result = new StringJoiner("|");
|
||||
appendFlag(result, flags, FLAG_RESUMED, "resumed");
|
||||
appendFlag(result, flags, FLAG_TRANSITION_TO_RESUMED, "transition_to_resumed");
|
||||
appendFlag(result, flags, FLAG_VISIBLE, "flag_visible");
|
||||
appendFlag(result, flags, FLAG_TRANSITION_TO_VISIBLE, "transition_to_visible");
|
||||
appendFlag(result, flags, FLAG_LAUNCHER_IN_STATE_TRANSITION,
|
||||
"launcher_in_state_transition");
|
||||
appendFlag(result, flags, FLAG_AWAKE, "awake");
|
||||
|
||||
@@ -18,33 +18,37 @@ package com.android.launcher3.taskbar;
|
||||
import static android.content.Context.RECEIVER_NOT_EXPORTED;
|
||||
import static android.content.pm.PackageManager.FEATURE_PC;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
|
||||
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
|
||||
|
||||
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
|
||||
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
|
||||
import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
|
||||
import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
|
||||
import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
|
||||
import static com.android.quickstep.util.SystemActionConstants.ACTION_SHOW_TASKBAR;
|
||||
import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentCallbacks;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.Trace;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -53,12 +57,11 @@ import androidx.annotation.VisibleForTesting;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.statemanager.StatefulActivity;
|
||||
import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.SettingsCache;
|
||||
import com.android.launcher3.util.SimpleBroadcastReceiver;
|
||||
import com.android.quickstep.RecentsActivity;
|
||||
@@ -68,6 +71,7 @@ import com.android.quickstep.util.AssistUtils;
|
||||
import com.android.systemui.shared.system.QuickStepContract;
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
|
||||
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
|
||||
import com.android.wm.shell.Flags;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.StringJoiner;
|
||||
@@ -95,9 +99,6 @@ public class TaskbarManager {
|
||||
| ActivityInfo.CONFIG_SCREEN_LAYOUT
|
||||
| ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
|
||||
|
||||
public static final boolean FLAG_HIDE_NAVBAR_WINDOW =
|
||||
SystemProperties.getBoolean("persist.wm.debug.hide_navbar_window", false);
|
||||
|
||||
private static final Uri USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(
|
||||
Settings.Secure.USER_SETUP_COMPLETE);
|
||||
|
||||
@@ -105,6 +106,10 @@ public class TaskbarManager {
|
||||
Settings.Secure.NAV_BAR_KIDS_MODE);
|
||||
|
||||
private final Context mContext;
|
||||
private final @Nullable Context mNavigationBarPanelContext;
|
||||
private WindowManager mWindowManager;
|
||||
private FrameLayout mTaskbarRootLayout;
|
||||
private boolean mAddedWindow;
|
||||
private final TaskbarNavButtonController mNavButtonController;
|
||||
private final ComponentCallbacks mComponentCallbacks;
|
||||
|
||||
@@ -135,44 +140,28 @@ public class TaskbarManager {
|
||||
|
||||
private boolean mUserUnlocked = false;
|
||||
|
||||
public static final int SYSTEM_ACTION_ID_TASKBAR = 499;
|
||||
|
||||
/**
|
||||
* For Taskbar broadcast intent filter.
|
||||
*/
|
||||
public static final String ACTION_SHOW_TASKBAR = "ACTION_SHOW_TASKBAR";
|
||||
|
||||
private final SimpleBroadcastReceiver mTaskbarBroadcastReceiver =
|
||||
new SimpleBroadcastReceiver(this::showTaskbarFromBroadcast);
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener
|
||||
mTaskbarPinningPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
if (TASKBAR_PINNING_KEY.equals(key)) {
|
||||
recreateTaskbar();
|
||||
}
|
||||
};
|
||||
|
||||
private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks =
|
||||
new ActivityLifecycleCallbacksAdapter() {
|
||||
@Override
|
||||
public void onActivityDestroyed(Activity activity) {
|
||||
if (mActivity != activity) return;
|
||||
if (mActivity != null) {
|
||||
mActivity.removeOnDeviceProfileChangeListener(
|
||||
mDebugActivityDeviceProfileChanged);
|
||||
Log.d(TASKBAR_NOT_DESTROYED_TAG,
|
||||
"unregistering activity lifecycle callbacks from "
|
||||
+ "onActivityDestroyed.");
|
||||
mActivity.unregisterActivityLifecycleCallbacks(this);
|
||||
}
|
||||
mActivity = null;
|
||||
debugWhyTaskbarNotDestroyed("clearActivity");
|
||||
if (mTaskbarActivityContext != null) {
|
||||
mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
|
||||
}
|
||||
mUnfoldProgressProvider.setSourceProvider(null);
|
||||
}
|
||||
};
|
||||
private final Runnable mActivityOnDestroyCallback = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mActivity != null) {
|
||||
mActivity.removeOnDeviceProfileChangeListener(
|
||||
mDebugActivityDeviceProfileChanged);
|
||||
Log.d(TASKBAR_NOT_DESTROYED_TAG,
|
||||
"unregistering activity lifecycle callbacks from "
|
||||
+ "onActivityDestroyed.");
|
||||
mActivity.removeEventCallback(EVENT_DESTROYED, this);
|
||||
}
|
||||
mActivity = null;
|
||||
debugWhyTaskbarNotDestroyed("clearActivity");
|
||||
if (mTaskbarActivityContext != null) {
|
||||
mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
|
||||
}
|
||||
mUnfoldProgressProvider.setSourceProvider(null);
|
||||
}
|
||||
};
|
||||
|
||||
UnfoldTransitionProgressProvider.TransitionProgressListener mUnfoldTransitionProgressListener =
|
||||
new UnfoldTransitionProgressProvider.TransitionProgressListener() {
|
||||
@@ -207,7 +196,27 @@ public class TaskbarManager {
|
||||
public TaskbarManager(TouchInteractionService service) {
|
||||
Display display =
|
||||
service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
|
||||
mContext = service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null);
|
||||
mContext = service.createWindowContext(display,
|
||||
ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL,
|
||||
null);
|
||||
mNavigationBarPanelContext = ENABLE_TASKBAR_NAVBAR_UNIFICATION
|
||||
? service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
|
||||
: null;
|
||||
if (enableTaskbarNoRecreate()) {
|
||||
mWindowManager = mContext.getSystemService(WindowManager.class);
|
||||
mTaskbarRootLayout = new FrameLayout(mContext) {
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||
// The motion events can be outside the view bounds of task bar, and hence
|
||||
// manually dispatching them to the drag layer here.
|
||||
if (mTaskbarActivityContext != null
|
||||
&& mTaskbarActivityContext.getDragLayer().isAttachedToWindow()) {
|
||||
return mTaskbarActivityContext.getDragLayer().dispatchTouchEvent(ev);
|
||||
}
|
||||
return super.dispatchTouchEvent(ev);
|
||||
}
|
||||
};
|
||||
}
|
||||
mNavButtonController = new TaskbarNavButtonController(service,
|
||||
SystemUiProxy.INSTANCE.get(mContext), new Handler(),
|
||||
AssistUtils.newInstance(mContext));
|
||||
@@ -244,7 +253,7 @@ public class TaskbarManager {
|
||||
destroyExistingTaskbar();
|
||||
} else {
|
||||
if (dp != null && isTaskbarPresent(dp)) {
|
||||
if (FLAG_HIDE_NAVBAR_WINDOW) {
|
||||
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
|
||||
// Re-initialize for screen size change? Should this be done
|
||||
// by looking at screen-size change flag in configDiff in the
|
||||
// block above?
|
||||
@@ -288,13 +297,16 @@ public class TaskbarManager {
|
||||
private void destroyExistingTaskbar() {
|
||||
debugWhyTaskbarNotDestroyed("destroyExistingTaskbar: " + mTaskbarActivityContext);
|
||||
if (mTaskbarActivityContext != null) {
|
||||
LauncherPrefs.get(mContext).removeListener(mTaskbarPinningPreferenceChangeListener,
|
||||
TASKBAR_PINNING);
|
||||
mTaskbarActivityContext.onDestroy();
|
||||
if (!FLAG_HIDE_NAVBAR_WINDOW) {
|
||||
if (!ENABLE_TASKBAR_NAVBAR_UNIFICATION || enableTaskbarNoRecreate()) {
|
||||
mTaskbarActivityContext = null;
|
||||
}
|
||||
}
|
||||
DeviceProfile dp = mUserUnlocked ?
|
||||
LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
|
||||
if (dp == null || !isTaskbarPresent(dp)) {
|
||||
removeTaskbarRootViewFromWindow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -322,7 +334,7 @@ public class TaskbarManager {
|
||||
return;
|
||||
}
|
||||
|
||||
mTaskbarActivityContext.toggleAllApps();
|
||||
mTaskbarActivityContext.toggleAllAppsSearch();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -343,6 +355,7 @@ public class TaskbarManager {
|
||||
mUserUnlocked = true;
|
||||
LauncherAppState.getIDP(mContext).addOnChangeListener(mIdpChangeListener);
|
||||
recreateTaskbar();
|
||||
addTaskbarRootViewToWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -358,7 +371,7 @@ public class TaskbarManager {
|
||||
mActivity.addOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
|
||||
Log.d(TASKBAR_NOT_DESTROYED_TAG,
|
||||
"registering activity lifecycle callbacks from setActivity().");
|
||||
mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
|
||||
mActivity.addEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback);
|
||||
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
|
||||
getUnfoldTransitionProgressProviderForActivity(activity);
|
||||
if (unfoldTransitionProgressProvider != null) {
|
||||
@@ -417,7 +430,7 @@ public class TaskbarManager {
|
||||
boolean isTaskbarEnabled = dp != null && isTaskbarPresent(dp);
|
||||
debugWhyTaskbarNotDestroyed("recreateTaskbar: isTaskbarEnabled=" + isTaskbarEnabled
|
||||
+ " [dp != null (i.e. mUserUnlocked)]=" + (dp != null)
|
||||
+ " FLAG_HIDE_NAVBAR_WINDOW=" + FLAG_HIDE_NAVBAR_WINDOW
|
||||
+ " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
|
||||
+ " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent));
|
||||
if (!isTaskbarEnabled) {
|
||||
SystemUiProxy.INSTANCE.get(mContext)
|
||||
@@ -425,13 +438,15 @@ public class TaskbarManager {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mTaskbarActivityContext == null) {
|
||||
mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp,
|
||||
mNavButtonController,
|
||||
mUnfoldProgressProvider);
|
||||
if (enableTaskbarNoRecreate() || mTaskbarActivityContext == null) {
|
||||
mTaskbarActivityContext = new TaskbarActivityContext(mContext,
|
||||
mNavigationBarPanelContext, dp, mNavButtonController,
|
||||
mUnfoldProgressProvider);
|
||||
} else {
|
||||
mTaskbarActivityContext.updateDeviceProfile(dp);
|
||||
}
|
||||
mSharedState.startTaskbarVariantIsTransient =
|
||||
DisplayController.isTransientTaskbar(mTaskbarActivityContext);
|
||||
mTaskbarActivityContext.init(mSharedState);
|
||||
|
||||
if (mActivity != null) {
|
||||
@@ -439,9 +454,12 @@ public class TaskbarManager {
|
||||
createTaskbarUIControllerForActivity(mActivity));
|
||||
}
|
||||
|
||||
// We to wait until user unlocks the device to attach listener.
|
||||
LauncherPrefs.get(mContext).addListener(mTaskbarPinningPreferenceChangeListener,
|
||||
TASKBAR_PINNING);
|
||||
if (enableTaskbarNoRecreate()) {
|
||||
addTaskbarRootViewToWindow();
|
||||
mTaskbarRootLayout.removeAllViews();
|
||||
mTaskbarRootLayout.addView(mTaskbarActivityContext.getDragLayer());
|
||||
mTaskbarActivityContext.notifyUpdateLayoutParams();
|
||||
}
|
||||
} finally {
|
||||
Trace.endSection();
|
||||
}
|
||||
@@ -479,7 +497,7 @@ public class TaskbarManager {
|
||||
* and we are using a single window for taskbar and navbar.
|
||||
*/
|
||||
public static boolean isPhoneMode(DeviceProfile deviceProfile) {
|
||||
return TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW && deviceProfile.isPhone;
|
||||
return ENABLE_TASKBAR_NAVBAR_UNIFICATION && deviceProfile.isPhone;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -491,7 +509,7 @@ public class TaskbarManager {
|
||||
}
|
||||
|
||||
private boolean isTaskbarPresent(DeviceProfile deviceProfile) {
|
||||
return FLAG_HIDE_NAVBAR_WINDOW || deviceProfile.isTaskbarPresent;
|
||||
return ENABLE_TASKBAR_NAVBAR_UNIFICATION || deviceProfile.isTaskbarPresent;
|
||||
}
|
||||
|
||||
public void onRotationProposal(int rotation, boolean isValid) {
|
||||
@@ -530,7 +548,7 @@ public class TaskbarManager {
|
||||
Log.d(TASKBAR_NOT_DESTROYED_TAG,
|
||||
"unregistering activity lifecycle callbacks from "
|
||||
+ "removeActivityCallbackAndListeners().");
|
||||
mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
|
||||
mActivity.removeEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback);
|
||||
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
|
||||
getUnfoldTransitionProgressProviderForActivity(mActivity);
|
||||
if (unfoldTransitionProgressProvider != null) {
|
||||
@@ -573,6 +591,21 @@ public class TaskbarManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void addTaskbarRootViewToWindow() {
|
||||
if (enableTaskbarNoRecreate() && !mAddedWindow && mTaskbarActivityContext != null) {
|
||||
mWindowManager.addView(mTaskbarRootLayout,
|
||||
mTaskbarActivityContext.getWindowLayoutParams());
|
||||
mAddedWindow = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeTaskbarRootViewFromWindow() {
|
||||
if (enableTaskbarNoRecreate() && mAddedWindow) {
|
||||
mWindowManager.removeViewImmediate(mTaskbarRootLayout);
|
||||
mAddedWindow = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Temp logs for b/254119092. */
|
||||
public void debugWhyTaskbarNotDestroyed(String debugReason) {
|
||||
StringJoiner log = new StringJoiner("\n");
|
||||
|
||||
@@ -27,6 +27,7 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP;
|
||||
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
|
||||
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
|
||||
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
|
||||
@@ -52,7 +53,6 @@ import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.TaskUtils;
|
||||
import com.android.quickstep.TouchInteractionService;
|
||||
import com.android.quickstep.util.AssistUtils;
|
||||
import com.android.quickstep.views.DesktopTaskView;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -274,7 +274,7 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
|
||||
private void navigateHome() {
|
||||
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
|
||||
|
||||
if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
|
||||
if (isDesktopModeSupported()) {
|
||||
DesktopVisibilityController desktopVisibilityController =
|
||||
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
|
||||
if (desktopVisibilityController != null) {
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.launcher3.taskbar
|
||||
|
||||
import android.animation.AnimatorSet
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.View
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.animation.doOnEnd
|
||||
import com.android.launcher3.LauncherPrefs
|
||||
import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
|
||||
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE
|
||||
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN
|
||||
import com.android.launcher3.taskbar.TaskbarDividerPopupView.Companion.createAndPopulate
|
||||
import java.io.PrintWriter
|
||||
|
||||
/** Controls taskbar pinning through a popup view. */
|
||||
class TaskbarPinningController(private val context: TaskbarActivityContext) :
|
||||
TaskbarControllers.LoggableTaskbarController {
|
||||
|
||||
private lateinit var controllers: TaskbarControllers
|
||||
private lateinit var taskbarSharedState: TaskbarSharedState
|
||||
private lateinit var launcherPrefs: LauncherPrefs
|
||||
private val statsLogManager = context.statsLogManager
|
||||
@VisibleForTesting var isAnimatingTaskbarPinning = false
|
||||
@VisibleForTesting lateinit var onCloseCallback: (preferenceChanged: Boolean) -> Unit
|
||||
|
||||
@SuppressLint("VisibleForTests")
|
||||
fun init(taskbarControllers: TaskbarControllers, sharedState: TaskbarSharedState) {
|
||||
controllers = taskbarControllers
|
||||
taskbarSharedState = sharedState
|
||||
launcherPrefs = context.launcherPrefs
|
||||
onCloseCallback =
|
||||
fun(didPreferenceChange: Boolean) {
|
||||
statsLogManager.logger().log(LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE)
|
||||
context.dragLayer.post { context.onPopupVisibilityChanged(false) }
|
||||
|
||||
if (!didPreferenceChange) {
|
||||
return
|
||||
}
|
||||
val animateToValue =
|
||||
if (!launcherPrefs.get(TASKBAR_PINNING)) {
|
||||
PINNING_PERSISTENT
|
||||
} else {
|
||||
PINNING_TRANSIENT
|
||||
}
|
||||
taskbarSharedState.taskbarWasPinned = animateToValue == PINNING_TRANSIENT
|
||||
animateTaskbarPinning(animateToValue)
|
||||
}
|
||||
}
|
||||
|
||||
fun showPinningView(view: View) {
|
||||
context.isTaskbarWindowFullscreen = true
|
||||
view.post {
|
||||
val popupView = getPopupView(view)
|
||||
popupView.requestFocus()
|
||||
popupView.onCloseCallback = onCloseCallback
|
||||
context.onPopupVisibilityChanged(true)
|
||||
popupView.show()
|
||||
statsLogManager.logger().log(LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN)
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun getPopupView(view: View): TaskbarDividerPopupView<*> {
|
||||
return createAndPopulate(view, context)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun animateTaskbarPinning(animateToValue: Float) {
|
||||
val taskbarViewController = controllers.taskbarViewController
|
||||
val animatorSet =
|
||||
getAnimatorSetForTaskbarPinningAnimation(animateToValue).apply {
|
||||
doOnEnd { recreateTaskbarAndUpdatePinningValue() }
|
||||
duration = PINNING_ANIMATION_DURATION
|
||||
}
|
||||
controllers.taskbarOverlayController.hideWindow()
|
||||
updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(true)
|
||||
taskbarViewController.animateAwayNotificationDotsDuringTaskbarPinningAnimation()
|
||||
animatorSet.start()
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun getAnimatorSetForTaskbarPinningAnimation(animateToValue: Float): AnimatorSet {
|
||||
val animatorSet = AnimatorSet()
|
||||
val taskbarViewController = controllers.taskbarViewController
|
||||
val dragLayerController = controllers.taskbarDragLayerController
|
||||
|
||||
animatorSet.playTogether(
|
||||
dragLayerController.taskbarBackgroundProgress.animateToValue(animateToValue),
|
||||
taskbarViewController.taskbarIconTranslationYForPinning.animateToValue(animateToValue),
|
||||
taskbarViewController.taskbarIconScaleForPinning.animateToValue(animateToValue),
|
||||
taskbarViewController.taskbarIconTranslationXForPinning.animateToValue(animateToValue)
|
||||
)
|
||||
|
||||
return animatorSet
|
||||
}
|
||||
|
||||
private fun updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(isAnimating: Boolean) {
|
||||
isAnimatingTaskbarPinning = isAnimating
|
||||
context.dragLayer.setAnimatingTaskbarPinning(isAnimating)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun recreateTaskbarAndUpdatePinningValue() {
|
||||
updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(false)
|
||||
launcherPrefs.put(TASKBAR_PINNING, !launcherPrefs.get(TASKBAR_PINNING))
|
||||
}
|
||||
|
||||
override fun dumpLogs(prefix: String, pw: PrintWriter) {
|
||||
pw.println(prefix + "TaskbarPinningController:")
|
||||
pw.println("$prefix\tisAnimatingTaskbarPinning=$isAnimatingTaskbarPinning")
|
||||
pw.println("$prefix\tTASKBAR_PINNING shared pref =" + launcherPrefs.get(TASKBAR_PINNING))
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PINNING_PERSISTENT = 1f
|
||||
const val PINNING_TRANSIENT = 0f
|
||||
const val PINNING_ANIMATION_DURATION = 500L
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_MATERIAL_U_POPUP;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
|
||||
|
||||
import android.content.Intent;
|
||||
@@ -163,19 +162,9 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (ENABLE_MATERIAL_U_POPUP.get()) {
|
||||
container = (PopupContainerWithArrow) context.getLayoutInflater().inflate(
|
||||
R.layout.popup_container_material_u, context.getDragLayer(), false);
|
||||
container.populateAndShowRowsMaterialU(icon, deepShortcutCount, systemShortcuts);
|
||||
} else {
|
||||
container = (PopupContainerWithArrow) context.getLayoutInflater().inflate(
|
||||
container = (PopupContainerWithArrow) context.getLayoutInflater().inflate(
|
||||
R.layout.popup_container, context.getDragLayer(), false);
|
||||
container.populateAndShow(
|
||||
icon,
|
||||
deepShortcutCount,
|
||||
mPopupDataProvider.getNotificationKeysForItem(item),
|
||||
systemShortcuts);
|
||||
}
|
||||
container.populateAndShowRows(icon, deepShortcutCount, systemShortcuts);
|
||||
|
||||
container.addOnAttachStateChangeListener(
|
||||
new PopupLiveUpdateHandler<BaseTaskbarContext>(context, container) {
|
||||
|
||||
@@ -70,6 +70,10 @@ public class TaskbarScrimView extends View {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
protected float getScrimAlpha() {
|
||||
return mRenderer.getPaint().getAlpha() / 255f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the roundness of the round corner above Taskbar.
|
||||
* @param cornerRoundness 0 has no round corner, 1 has complete round corner.
|
||||
|
||||
@@ -15,9 +15,13 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.launcher3.taskbar.bubbles.BubbleBarController.BUBBLE_BAR_ENABLED;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import static com.android.launcher3.taskbar.bubbles.BubbleBarController.isBubbleBarEnabled;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
|
||||
import static com.android.wm.shell.common.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.view.animation.Interpolator;
|
||||
@@ -35,13 +39,13 @@ import java.io.PrintWriter;
|
||||
public class TaskbarScrimViewController implements TaskbarControllers.LoggableTaskbarController,
|
||||
TaskbarControllers.BackgroundRendererController {
|
||||
|
||||
private static final float SCRIM_ALPHA = 0.6f;
|
||||
|
||||
private static final Interpolator SCRIM_ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
|
||||
private static final Interpolator SCRIM_ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
|
||||
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final TaskbarScrimView mScrimView;
|
||||
private boolean mTaskbarVisible;
|
||||
private int mSysUiStateFlags;
|
||||
|
||||
// Alpha property for the scrim.
|
||||
private final AnimatedFloat mScrimAlpha = new AnimatedFloat(this::updateScrimAlpha);
|
||||
@@ -61,36 +65,61 @@ public class TaskbarScrimViewController implements TaskbarControllers.LoggableTa
|
||||
mControllers = controllers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the taskbar visibility changes.
|
||||
*
|
||||
* @param visibility the current visibility of {@link TaskbarView}.
|
||||
*/
|
||||
public void onTaskbarVisibilityChanged(int visibility) {
|
||||
mTaskbarVisible = visibility == VISIBLE;
|
||||
if (shouldShowScrim()) {
|
||||
showScrim(true, getScrimAlpha(), false /* skipAnim */);
|
||||
} else if (mScrimView.getScrimAlpha() > 0f) {
|
||||
showScrim(false, 0, false /* skipAnim */);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the scrim state based on the flags.
|
||||
*/
|
||||
public void updateStateForSysuiFlags(int stateFlags, boolean skipAnim) {
|
||||
if (BUBBLE_BAR_ENABLED && DisplayController.isTransientTaskbar(mActivity)) {
|
||||
if (isBubbleBarEnabled() && DisplayController.isTransientTaskbar(mActivity)) {
|
||||
// These scrims aren't used if bubble bar & transient taskbar are active.
|
||||
return;
|
||||
}
|
||||
final boolean bubblesExpanded = (stateFlags & SYSUI_STATE_BUBBLES_EXPANDED) != 0;
|
||||
mSysUiStateFlags = stateFlags;
|
||||
showScrim(shouldShowScrim(), getScrimAlpha(), skipAnim);
|
||||
}
|
||||
|
||||
private boolean shouldShowScrim() {
|
||||
final boolean bubblesExpanded = (mSysUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) != 0;
|
||||
boolean isShadeVisible = (mSysUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE) != 0;
|
||||
return bubblesExpanded && !mControllers.navbarButtonsViewController.isImeVisible()
|
||||
&& !isShadeVisible
|
||||
&& !mControllers.taskbarStashController.isStashed()
|
||||
&& mTaskbarVisible;
|
||||
}
|
||||
|
||||
private float getScrimAlpha() {
|
||||
final boolean manageMenuExpanded =
|
||||
(stateFlags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
|
||||
final boolean showScrim = !mControllers.navbarButtonsViewController.isImeVisible()
|
||||
&& bubblesExpanded
|
||||
&& mControllers.taskbarStashController.isTaskbarVisibleAndNotStashing();
|
||||
final float scrimAlpha = manageMenuExpanded
|
||||
(mSysUiStateFlags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
|
||||
return manageMenuExpanded
|
||||
// When manage menu shows there's the first scrim and second scrim so figure out
|
||||
// what the total transparency would be.
|
||||
? (SCRIM_ALPHA + (SCRIM_ALPHA * (1 - SCRIM_ALPHA)))
|
||||
: showScrim ? SCRIM_ALPHA : 0;
|
||||
showScrim(showScrim, scrimAlpha, skipAnim);
|
||||
? (BUBBLE_EXPANDED_SCRIM_ALPHA + (BUBBLE_EXPANDED_SCRIM_ALPHA
|
||||
* (1 - BUBBLE_EXPANDED_SCRIM_ALPHA)))
|
||||
: shouldShowScrim() ? BUBBLE_EXPANDED_SCRIM_ALPHA : 0;
|
||||
}
|
||||
|
||||
private void showScrim(boolean showScrim, float alpha, boolean skipAnim) {
|
||||
mScrimView.setOnClickListener(showScrim ? (view) -> onClick() : null);
|
||||
mScrimView.setClickable(showScrim);
|
||||
ObjectAnimator anim = mScrimAlpha.animateToValue(showScrim ? alpha : 0);
|
||||
anim.setInterpolator(showScrim ? SCRIM_ALPHA_IN : SCRIM_ALPHA_OUT);
|
||||
anim.start();
|
||||
if (skipAnim) {
|
||||
anim.end();
|
||||
mScrimView.setScrimAlpha(alpha);
|
||||
} else {
|
||||
ObjectAnimator anim = mScrimAlpha.animateToValue(showScrim ? alpha : 0);
|
||||
anim.setInterpolator(showScrim ? SCRIM_ALPHA_IN : SCRIM_ALPHA_OUT);
|
||||
anim.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,15 +15,28 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static android.view.InsetsFrameProvider.SOURCE_DISPLAY;
|
||||
import static android.view.WindowInsets.Type.mandatorySystemGestures;
|
||||
import static android.view.WindowInsets.Type.navigationBars;
|
||||
import static android.view.WindowInsets.Type.systemGestures;
|
||||
import static android.view.WindowInsets.Type.tappableElement;
|
||||
|
||||
import static com.android.launcher3.taskbar.LauncherTaskbarUIController.DISPLAY_PROGRESS_COUNT;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.view.InsetsFrameProvider;
|
||||
|
||||
/**
|
||||
* State shared across different taskbar instance
|
||||
*/
|
||||
public class TaskbarSharedState {
|
||||
|
||||
private final IBinder mInsetsOwner = new Binder();
|
||||
private static int INDEX_LEFT = 0;
|
||||
private static int INDEX_RIGHT = 1;
|
||||
|
||||
// TaskbarManager#onSystemUiFlagsChanged
|
||||
public int sysuiStateFlags;
|
||||
|
||||
@@ -48,4 +61,33 @@ public class TaskbarSharedState {
|
||||
|
||||
// Taskbar System Action
|
||||
public PendingIntent taskbarSystemActionPendingIntent;
|
||||
|
||||
public final InsetsFrameProvider[] insetsFrameProviders = new InsetsFrameProvider[] {
|
||||
new InsetsFrameProvider(mInsetsOwner, 0, navigationBars()),
|
||||
new InsetsFrameProvider(mInsetsOwner, 0, tappableElement()),
|
||||
new InsetsFrameProvider(mInsetsOwner, 0, mandatorySystemGestures()),
|
||||
new InsetsFrameProvider(mInsetsOwner, INDEX_LEFT, systemGestures())
|
||||
.setSource(SOURCE_DISPLAY),
|
||||
new InsetsFrameProvider(mInsetsOwner, INDEX_RIGHT, systemGestures())
|
||||
.setSource(SOURCE_DISPLAY)
|
||||
};
|
||||
|
||||
// Allows us to shift translation logic when doing taskbar pinning animation.
|
||||
public boolean startTaskbarVariantIsTransient = true;
|
||||
|
||||
// To track if taskbar was pinned using taskbar pinning feature at the time of recreate,
|
||||
// so we can unstash transient taskbar when we un-pinning taskbar.
|
||||
private boolean mTaskbarWasPinned = false;
|
||||
|
||||
public boolean getTaskbarWasPinned() {
|
||||
return mTaskbarWasPinned;
|
||||
}
|
||||
|
||||
public void setTaskbarWasPinned(boolean taskbarWasPinned) {
|
||||
mTaskbarWasPinned = taskbarWasPinned;
|
||||
}
|
||||
|
||||
// To track if taskbar was stashed / unstashed between configuration changes (which recreates
|
||||
// the task bar).
|
||||
public Boolean taskbarWasStashedAuto = true;
|
||||
}
|
||||
|
||||
+6
-4
@@ -34,6 +34,7 @@ import com.android.launcher3.R;
|
||||
import com.android.launcher3.accessibility.BaseAccessibilityDelegate;
|
||||
import com.android.launcher3.logging.StatsLogManager;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.ItemInfoWithIcon;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.notification.NotificationListener;
|
||||
import com.android.launcher3.util.ShortcutUtil;
|
||||
@@ -84,9 +85,9 @@ public class TaskbarShortcutMenuAccessibilityDelegate
|
||||
|
||||
@Override
|
||||
protected boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) {
|
||||
if (item instanceof WorkspaceItemInfo
|
||||
if (item instanceof ItemInfoWithIcon
|
||||
&& (action == MOVE_TO_TOP_OR_LEFT || action == MOVE_TO_BOTTOM_OR_RIGHT)) {
|
||||
WorkspaceItemInfo info = (WorkspaceItemInfo) item;
|
||||
ItemInfoWithIcon info = (ItemInfoWithIcon) item;
|
||||
int side = action == MOVE_TO_TOP_OR_LEFT
|
||||
? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT;
|
||||
|
||||
@@ -97,10 +98,11 @@ public class TaskbarShortcutMenuAccessibilityDelegate
|
||||
.withInstanceId(instanceIds.second)
|
||||
.log(getLogEventForPosition(side));
|
||||
|
||||
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
|
||||
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
|
||||
&& item instanceof WorkspaceItemInfo) {
|
||||
SystemUiProxy.INSTANCE.get(mContext).startShortcut(
|
||||
info.getIntent().getPackage(),
|
||||
info.getDeepShortcutId(),
|
||||
((WorkspaceItemInfo) info).getDeepShortcutId(),
|
||||
side,
|
||||
/* bundleOpts= */ null,
|
||||
info.user,
|
||||
|
||||
@@ -15,24 +15,21 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static android.view.HapticFeedbackConstants.LONG_PRESS;
|
||||
import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
|
||||
|
||||
import static com.android.app.animation.Interpolators.EMPHASIZED;
|
||||
import static com.android.app.animation.Interpolators.FINAL_FRAME;
|
||||
import static com.android.app.animation.Interpolators.INSTANT;
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_SHOW;
|
||||
import static com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED;
|
||||
import static com.android.launcher3.taskbar.TaskbarManager.SYSTEM_ACTION_ID_TASKBAR;
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
|
||||
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
|
||||
import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
|
||||
@@ -43,7 +40,6 @@ import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.app.RemoteAction;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
@@ -61,9 +57,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
import com.android.internal.jank.InteractionJankMonitor;
|
||||
import com.android.launcher3.Alarm;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
@@ -85,26 +79,26 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
public static final int FLAG_IN_APP = 1 << 0;
|
||||
public static final int FLAG_STASHED_IN_APP_MANUAL = 1 << 1; // long press, persisted
|
||||
public static final int FLAG_STASHED_IN_APP_SYSUI = 1 << 2; // shade open, ...
|
||||
public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 3; // setup wizard and AllSetActivity
|
||||
public static final int FLAG_STASHED_IN_APP_IME = 1 << 4; // IME is visible
|
||||
public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 5;
|
||||
public static final int FLAG_STASHED_IN_TASKBAR_ALL_APPS = 1 << 6; // All apps is visible.
|
||||
public static final int FLAG_IN_SETUP = 1 << 7; // In the Setup Wizard
|
||||
public static final int FLAG_STASHED_SMALL_SCREEN = 1 << 8; // phone screen gesture nav, stashed
|
||||
public static final int FLAG_STASHED_IN_APP_AUTO = 1 << 9; // Autohide (transient taskbar).
|
||||
public static final int FLAG_STASHED_SYSUI = 1 << 10; // app pinning,...
|
||||
public static final int FLAG_STASHED_DEVICE_LOCKED = 1 << 11; // device is locked: keyguard, ...
|
||||
public static final int FLAG_STASHED_IN_APP_SYSUI = 1 << 1; // shade open, ...
|
||||
public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 2; // setup wizard and AllSetActivity
|
||||
public static final int FLAG_STASHED_IN_APP_IME = 1 << 3; // IME is visible
|
||||
public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 4;
|
||||
public static final int FLAG_STASHED_IN_TASKBAR_ALL_APPS = 1 << 5; // All apps is visible.
|
||||
public static final int FLAG_IN_SETUP = 1 << 6; // In the Setup Wizard
|
||||
public static final int FLAG_STASHED_SMALL_SCREEN = 1 << 7; // phone screen gesture nav, stashed
|
||||
public static final int FLAG_STASHED_IN_APP_AUTO = 1 << 8; // Autohide (transient taskbar).
|
||||
public static final int FLAG_STASHED_SYSUI = 1 << 9; // app pinning,...
|
||||
public static final int FLAG_STASHED_DEVICE_LOCKED = 1 << 10; // device is locked: keyguard, ...
|
||||
public static final int FLAG_IN_OVERVIEW = 1 << 11; // launcher is in overview
|
||||
|
||||
// If any of these flags are enabled, isInApp should return true.
|
||||
private static final int FLAGS_IN_APP = FLAG_IN_APP | FLAG_IN_SETUP;
|
||||
|
||||
// If we're in an app and any of these flags are enabled, taskbar should be stashed.
|
||||
private static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL
|
||||
| FLAG_STASHED_IN_APP_SYSUI | FLAG_STASHED_IN_APP_SETUP
|
||||
| FLAG_STASHED_IN_APP_IME | FLAG_STASHED_IN_TASKBAR_ALL_APPS
|
||||
| FLAG_STASHED_SMALL_SCREEN | FLAG_STASHED_IN_APP_AUTO;
|
||||
private static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_SYSUI
|
||||
| FLAG_STASHED_IN_APP_SETUP | FLAG_STASHED_IN_APP_IME
|
||||
| FLAG_STASHED_IN_TASKBAR_ALL_APPS | FLAG_STASHED_SMALL_SCREEN
|
||||
| FLAG_STASHED_IN_APP_AUTO;
|
||||
|
||||
// If any of these flags are enabled, inset apps by our stashed height instead of our unstashed
|
||||
// height. This way the reported insets are consistent even during transitions out of the app.
|
||||
@@ -164,21 +158,11 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
*/
|
||||
private static final long TASKBAR_STASH_ICON_ALPHA_HOME_TO_APP_START_DELAY = 66;
|
||||
|
||||
/**
|
||||
* The scale that TaskbarView animates to when hinting towards the stashed state.
|
||||
*/
|
||||
private static final float STASHED_TASKBAR_HINT_SCALE = 0.9f;
|
||||
|
||||
/**
|
||||
* The scale that the stashed handle animates to when hinting towards the unstashed state.
|
||||
*/
|
||||
private static final float UNSTASHED_TASKBAR_HANDLE_HINT_SCALE = 1.1f;
|
||||
|
||||
/**
|
||||
* The SharedPreferences key for whether user has manually stashed the taskbar.
|
||||
*/
|
||||
private static final String SHARED_PREFS_STASHED_KEY = "taskbar_is_stashed";
|
||||
|
||||
/**
|
||||
* Whether taskbar should be stashed out of the box.
|
||||
*/
|
||||
@@ -222,7 +206,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
private @interface StashAnimation {}
|
||||
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final SharedPreferences mPrefs;
|
||||
private final int mStashedHeight;
|
||||
private final int mUnstashedHeight;
|
||||
private final SystemUiProxy mSystemUiProxy;
|
||||
@@ -251,8 +234,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
private boolean mIsImeShowing;
|
||||
private boolean mIsImeSwitcherShowing;
|
||||
|
||||
private boolean mEnableManualStashingDuringTests = false;
|
||||
|
||||
private final Alarm mTimeoutAlarm = new Alarm();
|
||||
private boolean mEnableBlockingTimeoutDuringTests = false;
|
||||
|
||||
@@ -272,12 +253,18 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
public TaskbarStashController(TaskbarActivityContext activity) {
|
||||
mActivity = activity;
|
||||
mPrefs = LauncherPrefs.getPrefs(mActivity);
|
||||
mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
|
||||
mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class);
|
||||
|
||||
mUnstashedHeight = mActivity.getDeviceProfile().taskbarHeight;
|
||||
mStashedHeight = mActivity.getDeviceProfile().stashedTaskbarHeight;
|
||||
if (isPhoneMode()) {
|
||||
mUnstashedHeight = mActivity.getResources().getDimensionPixelSize(
|
||||
R.dimen.taskbar_phone_size);
|
||||
mStashedHeight = mActivity.getResources().getDimensionPixelSize(
|
||||
R.dimen.taskbar_stashed_size);
|
||||
} else {
|
||||
mUnstashedHeight = mActivity.getDeviceProfile().taskbarHeight;
|
||||
mStashedHeight = mActivity.getDeviceProfile().stashedTaskbarHeight;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,16 +307,13 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
mTaskbarStashedHandleHintScale = stashedHandleController.getStashedHandleHintScale();
|
||||
|
||||
boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
|
||||
// We use supportsVisualStashing() here instead of supportsManualStashing() because we want
|
||||
// it to work properly for tests that recreate taskbar. This check is here just to ensure
|
||||
// that taskbar unstashes when going to 3 button mode (supportsVisualStashing() false).
|
||||
boolean isManuallyStashedInApp = supportsVisualStashing()
|
||||
&& !isTransientTaskbar
|
||||
&& !ENABLE_TASKBAR_PINNING.get()
|
||||
&& mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
|
||||
boolean isInSetup = !mActivity.isUserSetupComplete() || setupUIVisible;
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, isTransientTaskbar);
|
||||
boolean isStashedInAppAuto =
|
||||
isTransientTaskbar && !mTaskbarSharedState.getTaskbarWasPinned();
|
||||
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
|
||||
isStashedInAppAuto = isStashedInAppAuto && mTaskbarSharedState.taskbarWasStashedAuto;
|
||||
}
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, isStashedInAppAuto);
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup);
|
||||
updateStateForFlag(FLAG_IN_SETUP, isInSetup);
|
||||
updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, isPhoneMode()
|
||||
@@ -338,6 +322,10 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
// us that we're paused until a bit later. This avoids flickering upon recreating taskbar.
|
||||
updateStateForFlag(FLAG_IN_APP, true);
|
||||
applyState(/* duration = */ 0);
|
||||
if (mTaskbarSharedState.getTaskbarWasPinned()
|
||||
|| !mTaskbarSharedState.taskbarWasStashedAuto) {
|
||||
tryStartTaskbarTimeout();
|
||||
}
|
||||
notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp());
|
||||
}
|
||||
|
||||
@@ -349,28 +337,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
return !mActivity.isThreeButtonNav() && mControllers.uiController.supportsVisualStashing();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the user can manually stash the taskbar based on the current device state.
|
||||
*/
|
||||
protected boolean supportsManualStashing() {
|
||||
if (ENABLE_TASKBAR_PINNING.get() && mPrefs.getBoolean(TASKBAR_PINNING_KEY, false)) {
|
||||
return false;
|
||||
}
|
||||
return supportsVisualStashing()
|
||||
&& isInApp()
|
||||
&& (!Utilities.isRunningInTestHarness() || mEnableManualStashingDuringTests)
|
||||
&& !DisplayController.isTransientTaskbar(mActivity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables support for manual stashing. This should only be used to add this functionality
|
||||
* to Launcher specific tests.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void enableManualStashingDuringTests(boolean enableManualStashing) {
|
||||
mEnableManualStashingDuringTests = enableManualStashing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the auto timeout for taskbar stashing. This method should only be used for taskbar
|
||||
* testing.
|
||||
@@ -532,6 +498,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
if (hasAnyFlag(FLAG_STASHED_IN_APP_AUTO) != stash) {
|
||||
mTaskbarSharedState.taskbarWasStashedAuto = stash;
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, stash);
|
||||
applyState();
|
||||
}
|
||||
@@ -565,53 +532,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
/* shouldBubblesFollow= */ !bubbleBarExpanded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when long pressing the nav region when taskbar is present.
|
||||
* @return Whether taskbar was stashed and now is unstashed.
|
||||
*/
|
||||
public boolean onLongPressToUnstashTaskbar() {
|
||||
if (!isStashed()) {
|
||||
// We only listen for long press on the nav region to unstash the taskbar. To stash the
|
||||
// taskbar, we use an OnLongClickListener on TaskbarView instead.
|
||||
return false;
|
||||
}
|
||||
if (!canCurrentlyManuallyUnstash()) {
|
||||
return false;
|
||||
}
|
||||
if (updateAndAnimateIsManuallyStashedInApp(false)) {
|
||||
mControllers.taskbarActivityContext.getDragLayer().performHapticFeedback(LONG_PRESS);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether taskbar will unstash when long pressing it based on the current state. The
|
||||
* only time this is true is if the user is in an app and the taskbar is only stashed because
|
||||
* the user previously long pressed to manually stash (not due to other reasons like IME).
|
||||
*/
|
||||
private boolean canCurrentlyManuallyUnstash() {
|
||||
return (mState & (FLAG_IN_APP | FLAGS_STASHED_IN_APP))
|
||||
== (FLAG_IN_APP | FLAG_STASHED_IN_APP_MANUAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates whether we should stash the taskbar when in apps, and animates to the changed state.
|
||||
* @return Whether we started an animation to either be newly stashed or unstashed.
|
||||
*/
|
||||
public boolean updateAndAnimateIsManuallyStashedInApp(boolean isManuallyStashedInApp) {
|
||||
if (!supportsManualStashing()) {
|
||||
return false;
|
||||
}
|
||||
if (hasAnyFlag(FLAG_STASHED_IN_APP_MANUAL) != isManuallyStashedInApp) {
|
||||
mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_KEY, isManuallyStashedInApp).apply();
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
|
||||
applyState();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Toggles the Taskbar's stash state. */
|
||||
public void toggleTaskbarStash() {
|
||||
if (!DisplayController.isTransientTaskbar(mActivity) || !hasAnyFlag(FLAGS_IN_APP)) return;
|
||||
@@ -898,21 +818,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Creates and starts a partial stash animation, hinting at the new state that will trigger when
|
||||
* long press is detected.
|
||||
* @param animateForward Whether we are going towards the new stashed state or returning to the
|
||||
* unstashed state.
|
||||
*/
|
||||
public void startStashHint(boolean animateForward) {
|
||||
if (isStashed() || !supportsManualStashing()) {
|
||||
// Already stashed, no need to hint in that direction.
|
||||
return;
|
||||
}
|
||||
mIconScaleForStash.animateToValue(
|
||||
animateForward ? STASHED_TASKBAR_HINT_SCALE : 1)
|
||||
.setDuration(TASKBAR_HINT_STASH_DURATION).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and starts a partial unstash animation, hinting at the new state that will trigger
|
||||
@@ -920,19 +825,12 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
*
|
||||
* @param animateForward Whether we are going towards the new unstashed state or returning to
|
||||
* the stashed state.
|
||||
* @param forceUnstash Whether we force the unstash hint to animate.
|
||||
*/
|
||||
protected void startUnstashHint(boolean animateForward, boolean forceUnstash) {
|
||||
protected void startUnstashHint(boolean animateForward) {
|
||||
if (!isStashed()) {
|
||||
// Already unstashed, no need to hint in that direction.
|
||||
return;
|
||||
}
|
||||
// TODO(b/270395798): Clean up after removing long-press unstashing code path.
|
||||
if (!canCurrentlyManuallyUnstash() && !forceUnstash) {
|
||||
// If any other flags are causing us to be stashed, long press won't cause us to
|
||||
// unstash, so don't hint that it will.
|
||||
return;
|
||||
}
|
||||
mTaskbarStashedHandleHintScale.animateToValue(
|
||||
animateForward ? UNSTASHED_TASKBAR_HANDLE_HINT_SCALE : 1)
|
||||
.setDuration(TASKBAR_HINT_STASH_DURATION).start();
|
||||
@@ -1015,9 +913,12 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_SYSUI, hasAnyFlag(systemUiStateFlags,
|
||||
SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE));
|
||||
updateStateForFlag(FLAG_STASHED_SYSUI,
|
||||
hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING));
|
||||
|
||||
boolean stashForBubbles = hasAnyFlag(FLAG_IN_OVERVIEW)
|
||||
&& hasAnyFlag(systemUiStateFlags, SYSUI_STATE_BUBBLES_EXPANDED)
|
||||
&& DisplayController.isTransientTaskbar(mActivity);
|
||||
updateStateForFlag(FLAG_STASHED_SYSUI,
|
||||
hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING) || stashForBubbles);
|
||||
boolean isLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED)
|
||||
&& !hasAnyFlag(systemUiStateFlags, SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY);
|
||||
updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, isLocked);
|
||||
@@ -1040,10 +941,10 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
* * in small screen AND
|
||||
* * 3 button nav AND
|
||||
* * landscape (or seascape)
|
||||
* We do not stash if taskbar is transient
|
||||
* We do not stash if taskbar is transient or hardware keyboard is active.
|
||||
*/
|
||||
private boolean shouldStashForIme() {
|
||||
if (DisplayController.isTransientTaskbar(mActivity)) {
|
||||
if (DisplayController.isTransientTaskbar(mActivity) || mActivity.isHardwareKeyboard()) {
|
||||
return false;
|
||||
}
|
||||
return (mIsImeShowing || mIsImeSwitcherShowing) &&
|
||||
@@ -1082,13 +983,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
mControllers.taskbarAutohideSuspendController.updateFlag(
|
||||
TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER, !isInApp());
|
||||
}
|
||||
if (hasAnyFlag(changedFlags, FLAG_STASHED_IN_APP_MANUAL)) {
|
||||
if (hasAnyFlag(FLAG_STASHED_IN_APP_MANUAL)) {
|
||||
mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_LONGPRESS_HIDE);
|
||||
} else {
|
||||
mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_LONGPRESS_SHOW);
|
||||
}
|
||||
}
|
||||
if (hasAnyFlag(changedFlags, FLAG_STASHED_IN_APP_AUTO)) {
|
||||
mActivity.getStatsLogManager().logger().log(hasAnyFlag(FLAG_STASHED_IN_APP_AUTO)
|
||||
? LAUNCHER_TRANSIENT_TASKBAR_HIDE
|
||||
@@ -1212,7 +1106,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
private static String getStateString(int flags) {
|
||||
StringJoiner sj = new StringJoiner("|");
|
||||
appendFlag(sj, flags, FLAGS_IN_APP, "FLAG_IN_APP");
|
||||
appendFlag(sj, flags, FLAG_STASHED_IN_APP_MANUAL, "FLAG_STASHED_IN_APP_MANUAL");
|
||||
appendFlag(sj, flags, FLAG_STASHED_IN_APP_SYSUI, "FLAG_STASHED_IN_APP_SYSUI");
|
||||
appendFlag(sj, flags, FLAG_STASHED_IN_APP_SETUP, "FLAG_STASHED_IN_APP_SETUP");
|
||||
appendFlag(sj, flags, FLAG_STASHED_IN_APP_IME, "FLAG_STASHED_IN_APP_IME");
|
||||
|
||||
@@ -30,6 +30,7 @@ import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.ItemInfoWithIcon;
|
||||
@@ -43,7 +44,6 @@ import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -156,7 +156,7 @@ public class TaskbarUIController {
|
||||
mControllers.taskbarActivityContext.startTranslationSpring();
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* @param ev MotionEvent in screen coordinates.
|
||||
* @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
|
||||
*/
|
||||
@@ -165,6 +165,14 @@ public class TaskbarUIController {
|
||||
|| mControllers.navbarButtonsViewController.isEventOverAnyItem(ev);
|
||||
}
|
||||
|
||||
/** Checks if the given {@link MotionEvent} is over the bubble bar stash handle. */
|
||||
public boolean isEventOverBubbleBarStashHandle(MotionEvent ev) {
|
||||
return mControllers.bubbleControllers.map(
|
||||
bubbleControllers ->
|
||||
bubbleControllers.bubbleStashController.isEventOverStashHandle(ev))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if icons should be aligned to hotseat in the current transition.
|
||||
*/
|
||||
@@ -209,6 +217,7 @@ public class TaskbarUIController {
|
||||
|
||||
recentsView.getSplitSelectController().findLastActiveTasksAndRunCallback(
|
||||
Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()),
|
||||
false /* findExactPairMatch */,
|
||||
foundTasks -> {
|
||||
@Nullable Task foundTask = foundTasks.get(0);
|
||||
splitSelectSource.alreadyRunningTaskId = foundTask == null
|
||||
@@ -227,6 +236,7 @@ public class TaskbarUIController {
|
||||
RecentsView recents = getRecentsView();
|
||||
recents.getSplitSelectController().findLastActiveTasksAndRunCallback(
|
||||
Collections.singletonList(info.getComponentKey()),
|
||||
false /* findExactPairMatch */,
|
||||
foundTasks -> {
|
||||
@Nullable Task foundTask = foundTasks.get(0);
|
||||
if (foundTask != null) {
|
||||
@@ -290,11 +300,9 @@ public class TaskbarUIController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the focused task in splitscreen.
|
||||
*
|
||||
* No-op if the view is not yet open.
|
||||
* Launches the given task in split-screen.
|
||||
*/
|
||||
public void launchSplitTasks(@NonNull View taskview, @NonNull GroupTask groupTask) { }
|
||||
public void launchSplitTasks(@NonNull GroupTask groupTask) { }
|
||||
|
||||
/**
|
||||
* Returns the matching view (if any) in the taskbar.
|
||||
@@ -322,6 +330,14 @@ public class TaskbarUIController {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for when launcher state transition completes after user swipes to home.
|
||||
* @param finalState The final state of the transition.
|
||||
*/
|
||||
public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) {
|
||||
// Overridden
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the resumed state of this ui controller.
|
||||
*/
|
||||
@@ -336,4 +352,7 @@ public class TaskbarUIController {
|
||||
.stream()
|
||||
.map(mControllers.taskbarPopupController::createSplitShortcutFactory);
|
||||
}
|
||||
|
||||
/** Adjusts the hotseat for the bubble bar. */
|
||||
public void adjustHotseatForBubbleBar(boolean isBubbleBarVisible) {}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,11 @@ package com.android.launcher3.taskbar;
|
||||
import static android.content.pm.PackageManager.FEATURE_PC;
|
||||
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
|
||||
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_CURSOR_HOVER_STATES;
|
||||
import static com.android.launcher3.Flags.enableCursorHoverStates;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR;
|
||||
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
|
||||
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -27,12 +31,15 @@ import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.InputDevice;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.DimenRes;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -42,7 +49,7 @@ import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Insettable;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.apppairs.AppPairIcon;
|
||||
import com.android.launcher3.folder.FolderIcon;
|
||||
import com.android.launcher3.icons.ThemedIconDrawable;
|
||||
import com.android.launcher3.model.data.FolderInfo;
|
||||
@@ -90,11 +97,9 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
// Only non-null when device supports having an All Apps button.
|
||||
private @Nullable IconButtonView mTaskbarDivider;
|
||||
|
||||
private View mQsb;
|
||||
private final View mQsb;
|
||||
|
||||
private float mTransientTaskbarMinWidth;
|
||||
|
||||
private float mTransientTaskbarAllAppsButtonTranslationXOffset;
|
||||
private final float mTransientTaskbarMinWidth;
|
||||
|
||||
private boolean mShouldTryStartAlign;
|
||||
|
||||
@@ -120,17 +125,17 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivityContext)
|
||||
&& !TaskbarManager.isPhoneMode(mActivityContext.getDeviceProfile());
|
||||
mIsRtl = Utilities.isRtl(resources);
|
||||
mTransientTaskbarMinWidth = mContext.getResources().getDimension(
|
||||
R.dimen.transient_taskbar_min_width);
|
||||
mTransientTaskbarAllAppsButtonTranslationXOffset =
|
||||
resources.getDimension(isTransientTaskbar
|
||||
? R.dimen.transient_taskbar_all_apps_button_translation_x_offset
|
||||
: R.dimen.taskbar_all_apps_button_translation_x_offset);
|
||||
mTransientTaskbarMinWidth = resources.getDimension(R.dimen.transient_taskbar_min_width);
|
||||
|
||||
|
||||
onDeviceProfileChanged(mActivityContext.getDeviceProfile());
|
||||
|
||||
int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
|
||||
int actualIconSize = mActivityContext.getDeviceProfile().taskbarIconSize;
|
||||
if (enableTaskbarPinning()) {
|
||||
DeviceProfile deviceProfile = mActivityContext.getTransientTaskbarDeviceProfile();
|
||||
actualIconSize = deviceProfile.taskbarIconSize;
|
||||
}
|
||||
int visualIconSize = (int) (actualIconSize * ICON_VISIBLE_AREA_FACTOR);
|
||||
|
||||
mIconTouchSize = Math.max(actualIconSize,
|
||||
@@ -138,8 +143,11 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
|
||||
// We layout the icons to be of mIconTouchSize in width and height
|
||||
mItemMarginLeftRight = actualMargin - (mIconTouchSize - visualIconSize) / 2;
|
||||
mItemPadding = (mIconTouchSize - actualIconSize) / 2;
|
||||
|
||||
// We always layout taskbar as a transient taskbar when we have taskbar pinning feature on,
|
||||
// then we scale and translate the icons to match persistent taskbar designs, so we use
|
||||
// taskbar icon size from current device profile to calculate correct item padding.
|
||||
mItemPadding = (mIconTouchSize - mActivityContext.getDeviceProfile().taskbarIconSize) / 2;
|
||||
mFolderLeaveBehindColor = Themes.getAttrColor(mActivityContext,
|
||||
android.R.attr.textColorTertiary);
|
||||
|
||||
@@ -149,15 +157,13 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
if (!mActivityContext.getPackageManager().hasSystemFeature(FEATURE_PC)) {
|
||||
mAllAppsButton = (IconButtonView) LayoutInflater.from(context)
|
||||
.inflate(R.layout.taskbar_all_apps_button, this, false);
|
||||
mAllAppsButton.setIconDrawable(resources.getDrawable(isTransientTaskbar
|
||||
? R.drawable.ic_transient_taskbar_all_apps_button
|
||||
: R.drawable.ic_taskbar_all_apps_button));
|
||||
mAllAppsButton.setScaleX(mIsRtl ? -1 : 1);
|
||||
mAllAppsButton.setIconDrawable(resources.getDrawable(
|
||||
getAllAppsButton(isTransientTaskbar)));
|
||||
mAllAppsButton.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
|
||||
mAllAppsButton.setForegroundTint(
|
||||
mActivityContext.getColor(R.color.all_apps_button_color));
|
||||
|
||||
if (FeatureFlags.ENABLE_TASKBAR_PINNING.get()) {
|
||||
if (enableTaskbarPinning()) {
|
||||
mTaskbarDivider = (IconButtonView) LayoutInflater.from(context).inflate(
|
||||
R.layout.taskbar_divider,
|
||||
this, false);
|
||||
@@ -171,6 +177,42 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
private int getAllAppsButton(boolean isTransientTaskbar) {
|
||||
boolean shouldSelectTransientIcon =
|
||||
(isTransientTaskbar || enableTaskbarPinning())
|
||||
&& !mActivityContext.isThreeButtonNav();
|
||||
if (ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
|
||||
return shouldSelectTransientIcon
|
||||
? R.drawable.ic_transient_taskbar_all_apps_search_button
|
||||
: R.drawable.ic_taskbar_all_apps_search_button;
|
||||
} else {
|
||||
return shouldSelectTransientIcon
|
||||
? R.drawable.ic_transient_taskbar_all_apps_button
|
||||
: R.drawable.ic_taskbar_all_apps_button;
|
||||
}
|
||||
}
|
||||
|
||||
@DimenRes
|
||||
public int getAllAppsButtonTranslationXOffset(boolean isTransientTaskbar) {
|
||||
if (isTransientTaskbar) {
|
||||
return R.dimen.transient_taskbar_all_apps_button_translation_x_offset;
|
||||
} else {
|
||||
return ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()
|
||||
? R.dimen.taskbar_all_apps_search_button_translation_x_offset
|
||||
: R.dimen.taskbar_all_apps_button_translation_x_offset;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisibility(int visibility) {
|
||||
boolean changed = getVisibility() != visibility;
|
||||
super.setVisibility(visibility);
|
||||
if (changed && mControllerCallbacks != null) {
|
||||
mControllerCallbacks.notifyVisibilityChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
@@ -222,14 +264,14 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
mIconClickListener = mControllerCallbacks.getIconOnClickListener();
|
||||
mIconLongClickListener = mControllerCallbacks.getIconOnLongClickListener();
|
||||
|
||||
setOnLongClickListener(mControllerCallbacks.getBackgroundOnLongClickListener());
|
||||
|
||||
if (mAllAppsButton != null) {
|
||||
mAllAppsButton.setOnClickListener(mControllerCallbacks.getAllAppsButtonClickListener());
|
||||
}
|
||||
if (mTaskbarDivider != null) {
|
||||
mTaskbarDivider.setOnLongClickListener(
|
||||
mControllerCallbacks.getTaskbarDividerLongClickListener());
|
||||
mTaskbarDivider.setOnTouchListener(
|
||||
mControllerCallbacks.getTaskbarDividerRightClickListener());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,12 +310,14 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
|
||||
// Replace any Hotseat views with the appropriate type if it's not already that type.
|
||||
final int expectedLayoutResId;
|
||||
boolean isFolder = false;
|
||||
boolean isCollection = false;
|
||||
if (hotseatItemInfo.isPredictedItem()) {
|
||||
expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
|
||||
} else if (hotseatItemInfo instanceof FolderInfo) {
|
||||
expectedLayoutResId = R.layout.folder_icon;
|
||||
isFolder = true;
|
||||
} else if (hotseatItemInfo instanceof FolderInfo fi) {
|
||||
expectedLayoutResId = fi.itemType == ITEM_TYPE_APP_PAIR
|
||||
? R.layout.app_pair_icon
|
||||
: R.layout.folder_icon;
|
||||
isCollection = true;
|
||||
} else {
|
||||
expectedLayoutResId = R.layout.taskbar_app_icon;
|
||||
}
|
||||
@@ -284,7 +328,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
|
||||
// see if the view can be reused
|
||||
if ((hotseatView.getSourceLayoutResId() != expectedLayoutResId)
|
||||
|| (isFolder && (hotseatView.getTag() != hotseatItemInfo))) {
|
||||
|| (isCollection && (hotseatView.getTag() != hotseatItemInfo))) {
|
||||
// Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation,
|
||||
// so if the info changes we need to reinflate. This should only happen if a new
|
||||
// folder is dragged to the position that another folder previously existed.
|
||||
@@ -297,12 +341,23 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
}
|
||||
|
||||
if (hotseatView == null) {
|
||||
if (isFolder) {
|
||||
if (isCollection) {
|
||||
FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
|
||||
FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
|
||||
mActivityContext, this, folderInfo);
|
||||
folderIcon.setTextVisible(false);
|
||||
hotseatView = folderIcon;
|
||||
switch (hotseatItemInfo.itemType) {
|
||||
case ITEM_TYPE_FOLDER:
|
||||
hotseatView = FolderIcon.inflateFolderAndIcon(
|
||||
expectedLayoutResId, mActivityContext, this, folderInfo);
|
||||
((FolderIcon) hotseatView).setTextVisible(false);
|
||||
break;
|
||||
case ITEM_TYPE_APP_PAIR:
|
||||
hotseatView = AppPairIcon.inflateIcon(
|
||||
expectedLayoutResId, mActivityContext, this, folderInfo);
|
||||
((AppPairIcon) hotseatView).setTextVisible(false);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException(
|
||||
"Unexpected item type: " + hotseatItemInfo.itemType);
|
||||
}
|
||||
} else {
|
||||
hotseatView = inflate(expectedLayoutResId);
|
||||
}
|
||||
@@ -324,7 +379,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
}
|
||||
}
|
||||
setClickAndLongClickListenersForIcon(hotseatView);
|
||||
if (ENABLE_CURSOR_HOVER_STATES.get()) {
|
||||
if (enableCursorHoverStates()) {
|
||||
setHoverListenerForIcon(hotseatView);
|
||||
}
|
||||
nextViewIndex++;
|
||||
@@ -335,8 +390,6 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
}
|
||||
|
||||
if (mAllAppsButton != null) {
|
||||
mAllAppsButton.setTranslationXForTaskbarAllAppsIcon(getChildCount() > 0
|
||||
? mTransientTaskbarAllAppsButtonTranslationXOffset : 0f);
|
||||
addView(mAllAppsButton, mIsRtl ? getChildCount() : 0);
|
||||
|
||||
// if only all apps button present, don't include divider view.
|
||||
@@ -372,6 +425,16 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
public void setClickAndLongClickListenersForIcon(View icon) {
|
||||
icon.setOnClickListener(mIconClickListener);
|
||||
icon.setOnLongClickListener(mIconLongClickListener);
|
||||
// Add right-click support to btv icons.
|
||||
icon.setOnTouchListener((v, event) -> {
|
||||
if (event.isFromSource(InputDevice.SOURCE_MOUSE)
|
||||
&& (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0
|
||||
&& v instanceof BubbleTextView) {
|
||||
mActivityContext.showPopupMenuForIcon((BubbleTextView) v);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -464,24 +527,6 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (mIconLayoutBounds.left <= event.getX() && event.getX() <= mIconLayoutBounds.right) {
|
||||
// Don't allow long pressing between icons, or above/below them.
|
||||
return true;
|
||||
}
|
||||
if (mControllerCallbacks.onTouchEvent(event)) {
|
||||
int oldAction = event.getAction();
|
||||
try {
|
||||
event.setAction(MotionEvent.ACTION_CANCEL);
|
||||
return super.onTouchEvent(event);
|
||||
} finally {
|
||||
event.setAction(oldAction);
|
||||
}
|
||||
}
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's
|
||||
* touch bounds.
|
||||
@@ -509,7 +554,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
int iconLayoutBoundsWidth =
|
||||
countExcludingQsb * (mItemMarginLeftRight * 2 + mIconTouchSize);
|
||||
|
||||
if (FeatureFlags.ENABLE_TASKBAR_PINNING.get() && countExcludingQsb > 1) {
|
||||
if (enableTaskbarPinning() && countExcludingQsb > 1) {
|
||||
// We are removing 4 * mItemMarginLeftRight as there should be no space between
|
||||
// All Apps icon, divider icon, and first app icon in taskbar
|
||||
iconLayoutBoundsWidth -= mItemMarginLeftRight * 4;
|
||||
|
||||
@@ -21,14 +21,19 @@ import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
|
||||
import static com.android.launcher3.Utilities.squaredHypot;
|
||||
import static com.android.launcher3.Utilities.mapRange;
|
||||
import static com.android.launcher3.anim.AnimatedFloat.VALUE;
|
||||
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
|
||||
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
|
||||
import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT;
|
||||
import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT;
|
||||
import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
|
||||
import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
|
||||
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
|
||||
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM;
|
||||
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_PINNING_ANIM;
|
||||
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_REVEAL_ANIM;
|
||||
|
||||
import android.animation.Animator;
|
||||
@@ -38,6 +43,7 @@ import android.animation.ValueAnimator;
|
||||
import android.annotation.NonNull;
|
||||
import android.graphics.Rect;
|
||||
import android.util.Log;
|
||||
import android.view.InputDevice;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.animation.Interpolator;
|
||||
@@ -47,6 +53,7 @@ import androidx.core.graphics.ColorUtils;
|
||||
import androidx.core.view.OneShotPreDrawListener;
|
||||
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
@@ -61,11 +68,13 @@ import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.icons.ThemedIconDrawable;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.ItemInfoMatcher;
|
||||
import com.android.launcher3.util.LauncherBindableItemsContainer;
|
||||
import com.android.launcher3.util.MultiPropertyFactory;
|
||||
import com.android.launcher3.util.MultiTranslateDelegate;
|
||||
import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.launcher3.views.IconButtonView;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.function.Predicate;
|
||||
@@ -98,12 +107,27 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
this::updateTranslationY);
|
||||
private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat(
|
||||
this::updateTranslationY);
|
||||
|
||||
private final AnimatedFloat mTaskbarIconScaleForPinning = new AnimatedFloat(
|
||||
this::updateTaskbarIconsScale);
|
||||
|
||||
private final AnimatedFloat mTaskbarIconTranslationXForPinning = new AnimatedFloat(
|
||||
this::updateTaskbarIconTranslationXForPinning);
|
||||
|
||||
private final AnimatedFloat mTaskbarIconTranslationYForPinning = new AnimatedFloat(
|
||||
this::updateTranslationY);
|
||||
|
||||
private final View.OnLayoutChangeListener mTaskbarViewLayoutChangeListener =
|
||||
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
|
||||
-> updateTaskbarIconTranslationXForPinning();
|
||||
|
||||
|
||||
private AnimatedFloat mTaskbarNavButtonTranslationY;
|
||||
private AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay;
|
||||
private float mTaskbarIconTranslationYForSwipe;
|
||||
private float mTaskbarIconTranslationYForSpringOnStash;
|
||||
|
||||
private final int mTaskbarBottomMargin;
|
||||
private int mTaskbarBottomMargin;
|
||||
private final int mStashedHandleHeight;
|
||||
private final int mLauncherThemedIconsBackgroundColor;
|
||||
private final int mTaskbarThemedIconsBackgroundColor;
|
||||
@@ -131,8 +155,18 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
|
||||
private final boolean mIsRtl;
|
||||
|
||||
private final DeviceProfile mTransientTaskbarDp;
|
||||
private final DeviceProfile mPersistentTaskbarDp;
|
||||
|
||||
private final int mTransientIconSize;
|
||||
private final int mPersistentIconSize;
|
||||
|
||||
public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) {
|
||||
mActivity = activity;
|
||||
mTransientTaskbarDp = mActivity.getTransientTaskbarDeviceProfile();
|
||||
mPersistentTaskbarDp = mActivity.getPersistentTaskbarDeviceProfile();
|
||||
mTransientIconSize = mTransientTaskbarDp.taskbarIconSize;
|
||||
mPersistentIconSize = mPersistentTaskbarDp.taskbarIconSize;
|
||||
mTaskbarView = taskbarView;
|
||||
mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, NUM_ALPHA_CHANNELS);
|
||||
mTaskbarIconAlpha.setUpdateVisibility(true);
|
||||
@@ -158,10 +192,16 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
mControllers = controllers;
|
||||
mTaskbarView.init(new TaskbarViewCallbacks());
|
||||
mTaskbarView.getLayoutParams().height = isPhoneMode(mActivity.getDeviceProfile())
|
||||
? mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_size)
|
||||
? mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_phone_size)
|
||||
: mActivity.getDeviceProfile().taskbarHeight;
|
||||
|
||||
mTaskbarIconScaleForStash.updateValue(1f);
|
||||
float pinningValue = DisplayController.isTransientTaskbar(mActivity)
|
||||
? PINNING_TRANSIENT
|
||||
: PINNING_PERSISTENT;
|
||||
mTaskbarIconScaleForPinning.updateValue(pinningValue);
|
||||
mTaskbarIconTranslationYForPinning.updateValue(pinningValue);
|
||||
mTaskbarIconTranslationXForPinning.updateValue(pinningValue);
|
||||
|
||||
mModelCallbacks.init(controllers);
|
||||
if (mActivity.isUserSetupComplete()) {
|
||||
@@ -175,12 +215,15 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
|
||||
mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
|
||||
|
||||
if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
|
||||
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
|
||||
// This gets modified in NavbarButtonsViewController, but the initial value it reads
|
||||
// may be incorrect since it's state gets destroyed on taskbar recreate, so reset here
|
||||
mTaskbarIconAlpha.get(ALPHA_INDEX_SMALL_SCREEN)
|
||||
.animateToValue(isPhoneButtonNavMode(mActivity) ? 0 : 1).start();
|
||||
}
|
||||
if (enableTaskbarPinning()) {
|
||||
mTaskbarView.addOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,6 +234,9 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
if (enableTaskbarPinning()) {
|
||||
mTaskbarView.removeOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
|
||||
}
|
||||
LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
|
||||
mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
|
||||
mModelCallbacks.unregisterListeners();
|
||||
@@ -253,6 +299,18 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
return mTaskbarIconTranslationYForStash;
|
||||
}
|
||||
|
||||
public AnimatedFloat getTaskbarIconScaleForPinning() {
|
||||
return mTaskbarIconScaleForPinning;
|
||||
}
|
||||
|
||||
public AnimatedFloat getTaskbarIconTranslationXForPinning() {
|
||||
return mTaskbarIconTranslationXForPinning;
|
||||
}
|
||||
|
||||
public AnimatedFloat getTaskbarIconTranslationYForPinning() {
|
||||
return mTaskbarIconTranslationYForPinning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies scale properties for the entire TaskbarView (rather than individual icons).
|
||||
*/
|
||||
@@ -262,6 +320,80 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
mTaskbarView.setScaleY(scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies scale properties for the taskbar icons
|
||||
*/
|
||||
private void updateTaskbarIconsScale() {
|
||||
float scale = mTaskbarIconScaleForPinning.value;
|
||||
View[] iconViews = mTaskbarView.getIconViews();
|
||||
|
||||
float finalScale;
|
||||
if (mControllers.getSharedState().startTaskbarVariantIsTransient) {
|
||||
finalScale = mapRange(scale, 1f, ((float) mPersistentIconSize / mTransientIconSize));
|
||||
} else {
|
||||
finalScale = mapRange(scale, ((float) mTransientIconSize / mPersistentIconSize), 1f);
|
||||
}
|
||||
|
||||
for (int iconIndex = 0; iconIndex < iconViews.length; iconIndex++) {
|
||||
iconViews[iconIndex].setScaleX(finalScale);
|
||||
iconViews[iconIndex].setScaleY(finalScale);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate away taskbar icon notification dots during the taskbar pinning animation.
|
||||
*/
|
||||
public void animateAwayNotificationDotsDuringTaskbarPinningAnimation() {
|
||||
for (View iconView : mTaskbarView.getIconViews()) {
|
||||
if (iconView instanceof BubbleTextView && ((BubbleTextView) iconView).hasDot()) {
|
||||
((BubbleTextView) iconView).animateDotScale(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTaskbarIconTranslationXForPinning() {
|
||||
View[] iconViews = mTaskbarView.getIconViews();
|
||||
float scale = mTaskbarIconTranslationXForPinning.value;
|
||||
float taskbarCenterX =
|
||||
mTaskbarView.getLeft() + (mTaskbarView.getRight() - mTaskbarView.getLeft()) / 2.0f;
|
||||
|
||||
float finalMarginScale = mapRange(scale, 0f, mTransientIconSize - mPersistentIconSize);
|
||||
|
||||
float transientTaskbarAllAppsOffset = mActivity.getResources().getDimension(
|
||||
mTaskbarView.getAllAppsButtonTranslationXOffset(true));
|
||||
float persistentTaskbarAllAppsOffset = mActivity.getResources().getDimension(
|
||||
mTaskbarView.getAllAppsButtonTranslationXOffset(false));
|
||||
|
||||
float allAppIconTranslateRange = mapRange(scale, transientTaskbarAllAppsOffset,
|
||||
persistentTaskbarAllAppsOffset);
|
||||
|
||||
if (mIsRtl) {
|
||||
allAppIconTranslateRange *= -1;
|
||||
}
|
||||
|
||||
float halfIconCount = iconViews.length / 2.0f;
|
||||
for (int iconIndex = 0; iconIndex < iconViews.length; iconIndex++) {
|
||||
View iconView = iconViews[iconIndex];
|
||||
MultiTranslateDelegate translateDelegate =
|
||||
((Reorderable) iconView).getTranslateDelegate();
|
||||
float iconCenterX =
|
||||
iconView.getLeft() + (iconView.getRight() - iconView.getLeft()) / 2.0f;
|
||||
if (iconCenterX <= taskbarCenterX) {
|
||||
translateDelegate.getTranslationX(INDEX_TASKBAR_PINNING_ANIM).setValue(
|
||||
finalMarginScale * (halfIconCount - iconIndex));
|
||||
} else {
|
||||
translateDelegate.getTranslationX(INDEX_TASKBAR_PINNING_ANIM).setValue(
|
||||
-finalMarginScale * (iconIndex - halfIconCount));
|
||||
}
|
||||
|
||||
if (iconView.equals(mTaskbarView.getAllAppsButtonView()) && iconViews.length > 1) {
|
||||
((IconButtonView) iconView).setTranslationXForTaskbarAllAppsIcon(
|
||||
allAppIconTranslateRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the translation of the TaskbarView during the swipe up gesture.
|
||||
*/
|
||||
@@ -282,9 +414,40 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
mTaskbarView.setTranslationY(mTaskbarIconTranslationYForHome.value
|
||||
+ mTaskbarIconTranslationYForStash.value
|
||||
+ mTaskbarIconTranslationYForSwipe
|
||||
+ getTaskbarIconTranslationYForPinningValue()
|
||||
+ mTaskbarIconTranslationYForSpringOnStash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes translation y for taskbar pinning.
|
||||
*/
|
||||
private float getTaskbarIconTranslationYForPinningValue() {
|
||||
if (mControllers.getSharedState() == null) return 0f;
|
||||
|
||||
float scale = mTaskbarIconTranslationYForPinning.value;
|
||||
float taskbarIconTranslationYForPinningValue;
|
||||
|
||||
// transY is calculated here by adding/subtracting the taskbar bottom margin
|
||||
// aligning the icon bound to be at bottom of current taskbar view and then
|
||||
// finally placing the icon in the middle of new taskbar background height.
|
||||
if (mControllers.getSharedState().startTaskbarVariantIsTransient) {
|
||||
float transY =
|
||||
mTransientTaskbarDp.taskbarBottomMargin + (mTransientTaskbarDp.taskbarHeight
|
||||
- mTaskbarView.getIconLayoutBounds().bottom)
|
||||
- (mPersistentTaskbarDp.taskbarHeight
|
||||
- mTransientTaskbarDp.taskbarIconSize) / 2f;
|
||||
taskbarIconTranslationYForPinningValue = mapRange(scale, 0f, transY);
|
||||
} else {
|
||||
float transY =
|
||||
-mTransientTaskbarDp.taskbarBottomMargin + (mPersistentTaskbarDp.taskbarHeight
|
||||
- mTaskbarView.getIconLayoutBounds().bottom)
|
||||
- (mTransientTaskbarDp.taskbarHeight
|
||||
- mTransientTaskbarDp.taskbarIconSize) / 2f;
|
||||
taskbarIconTranslationYForPinningValue = mapRange(scale, transY, 0f);
|
||||
}
|
||||
return taskbarIconTranslationYForPinningValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Taskbar's themed icons background according to the progress between in-app/home.
|
||||
*/
|
||||
@@ -435,6 +598,11 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
* 1 => fully aligned
|
||||
*/
|
||||
public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
|
||||
if (isPhoneMode(launcherDp)) {
|
||||
mIconAlignControllerLazy = null;
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isHotseatIconOnTopWhenAligned =
|
||||
mControllers.uiController.isHotseatIconOnTopWhenAligned();
|
||||
boolean isStashed = mControllers.taskbarStashController.isStashed();
|
||||
@@ -453,19 +621,21 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
}
|
||||
}
|
||||
|
||||
/** Resets the icon alignment controller so that it can be recreated again later. */
|
||||
void resetIconAlignmentController() {
|
||||
mIconAlignControllerLazy = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an animation for aligning the Taskbar icons with the provided Launcher device profile
|
||||
*/
|
||||
private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
|
||||
PendingAnimation setter = new PendingAnimation(100);
|
||||
if (TaskbarManager.isPhoneButtonNavMode(mActivity)) {
|
||||
// No animation for icons in small-screen
|
||||
return setter.createPlaybackController();
|
||||
}
|
||||
|
||||
mOnControllerPreCreateCallback.run();
|
||||
DeviceProfile taskbarDp = mActivity.getDeviceProfile();
|
||||
Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
|
||||
boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
|
||||
|
||||
float scaleUp = ((float) launcherDp.iconSizePx) / taskbarDp.taskbarIconSize;
|
||||
int borderSpacing = launcherDp.hotseatBorderSpace;
|
||||
int hotseatCellSize = DeviceProfile.calculateCellWidth(
|
||||
@@ -492,6 +662,10 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight(
|
||||
anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight));
|
||||
|
||||
mTaskbarBottomMargin = isTransientTaskbar
|
||||
? mTransientTaskbarDp.taskbarBottomMargin
|
||||
: mPersistentTaskbarDp.taskbarBottomMargin;
|
||||
|
||||
for (int i = 0; i < mTaskbarView.getChildCount(); i++) {
|
||||
View child = mTaskbarView.getChildAt(i);
|
||||
boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonView();
|
||||
@@ -502,7 +676,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
// to avoid icons disappearing rather than fading out visually.
|
||||
setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0.8f, 1f));
|
||||
} else if ((isAllAppsButton && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get())
|
||||
|| (isTaskbarDividerView && FeatureFlags.ENABLE_TASKBAR_PINNING.get())) {
|
||||
|| (isTaskbarDividerView && enableTaskbarPinning())) {
|
||||
if (!isToHome
|
||||
&& mIsHotseatIconOnTopWhenAligned
|
||||
&& mIsStashed) {
|
||||
@@ -523,6 +697,8 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
+ launcherDp.hotseatQsbWidth / 2f
|
||||
: hotseatPadding.left - borderSpacing - launcherDp.hotseatQsbWidth / 2f;
|
||||
float childCenter = (child.getLeft() + child.getRight()) / 2f;
|
||||
childCenter += ((Reorderable) child).getTranslateDelegate().getTranslationX(
|
||||
INDEX_TASKBAR_PINNING_ANIM).getValue();
|
||||
float halfQsbIconWidthDiff =
|
||||
(launcherDp.hotseatQsbWidth - taskbarDp.taskbarIconSize) / 2f;
|
||||
float scale = ((float) taskbarDp.taskbarIconSize)
|
||||
@@ -555,13 +731,19 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
continue;
|
||||
}
|
||||
|
||||
int positionInHotseat;
|
||||
float positionInHotseat;
|
||||
if (isAllAppsButton) {
|
||||
// Note that there is no All Apps button in the hotseat, this position is only used
|
||||
// as its convenient for animation purposes.
|
||||
// Note that there is no All Apps button in the hotseat,
|
||||
// this position is only used as its convenient for animation purposes.
|
||||
positionInHotseat = Utilities.isRtl(child.getResources())
|
||||
? taskbarDp.numShownHotseatIcons
|
||||
: -1;
|
||||
} else if (isTaskbarDividerView) {
|
||||
// Note that there is no taskbar divider view in the hotseat,
|
||||
// this position is only used as its convenient for animation purposes.
|
||||
positionInHotseat = Utilities.isRtl(child.getResources())
|
||||
? taskbarDp.numShownHotseatIcons - 0.5f
|
||||
: -0.5f;
|
||||
} else if (child.getTag() instanceof ItemInfo) {
|
||||
positionInHotseat = ((ItemInfo) child.getTag()).screenId;
|
||||
} else {
|
||||
@@ -569,14 +751,24 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
continue;
|
||||
}
|
||||
|
||||
float hotseatIconCenter = hotseatPadding.left
|
||||
+ (hotseatCellSize + borderSpacing) * positionInHotseat
|
||||
+ hotseatCellSize / 2f;
|
||||
float hotseatAdjustedBorderSpace =
|
||||
launcherDp.getHotseatAdjustedBorderSpaceForBubbleBar(child.getContext());
|
||||
float hotseatIconCenter;
|
||||
if (bubbleBarHasBubbles() && hotseatAdjustedBorderSpace != 0) {
|
||||
hotseatIconCenter = hotseatPadding.left + hotseatCellSize
|
||||
+ (hotseatCellSize + hotseatAdjustedBorderSpace) * positionInHotseat
|
||||
+ hotseatCellSize / 2f;
|
||||
} else {
|
||||
hotseatIconCenter = hotseatPadding.left
|
||||
+ (hotseatCellSize + borderSpacing) * positionInHotseat
|
||||
+ hotseatCellSize / 2f;
|
||||
}
|
||||
float childCenter = (child.getLeft() + child.getRight()) / 2f;
|
||||
childCenter += ((Reorderable) child).getTranslateDelegate().getTranslationX(
|
||||
INDEX_TASKBAR_PINNING_ANIM).getValue();
|
||||
float toX = hotseatIconCenter - childCenter;
|
||||
if (child instanceof Reorderable) {
|
||||
MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
|
||||
|
||||
setter.setFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM),
|
||||
MULTI_PROPERTY_VALUE, toX, interpolator);
|
||||
setter.setFloat(mtd.getTranslationY(INDEX_TASKBAR_ALIGNMENT_ANIM),
|
||||
@@ -593,6 +785,11 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
return controller;
|
||||
}
|
||||
|
||||
private boolean bubbleBarHasBubbles() {
|
||||
return mControllers.bubbleControllers.isPresent()
|
||||
&& mControllers.bubbleControllers.get().bubbleBarViewController.hasBubbles();
|
||||
}
|
||||
|
||||
public void onRotationChanged(DeviceProfile deviceProfile) {
|
||||
if (!mControllers.uiController.isIconAlignedWithHotseat()) {
|
||||
// We only translate on rotation when icon is aligned with hotseat
|
||||
@@ -686,13 +883,19 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
};
|
||||
}
|
||||
|
||||
public View.OnLongClickListener getIconOnLongClickListener() {
|
||||
return mControllers.taskbarDragController::startDragOnLongClick;
|
||||
public View.OnTouchListener getTaskbarDividerRightClickListener() {
|
||||
return (v, event) -> {
|
||||
if (event.isFromSource(InputDevice.SOURCE_MOUSE)
|
||||
&& event.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
|
||||
mControllers.taskbarPinningController.showPinningView(v);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
public View.OnLongClickListener getBackgroundOnLongClickListener() {
|
||||
return view -> mControllers.taskbarStashController
|
||||
.updateAndAnimateIsManuallyStashedInApp(true);
|
||||
public View.OnLongClickListener getIconOnLongClickListener() {
|
||||
return mControllers.taskbarDragController::startDragOnLongClick;
|
||||
}
|
||||
|
||||
/** Gets the hover listener for the provided icon view. */
|
||||
@@ -700,47 +903,19 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
return new TaskbarHoverToolTipController(mActivity, mTaskbarView, icon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first chance to handle TaskbarView#onTouchEvent, and return whether we want to
|
||||
* consume the touch so TaskbarView treats it as an ACTION_CANCEL.
|
||||
* TODO(b/270395798): We can remove this entirely once we remove the Transient Taskbar flag.
|
||||
*/
|
||||
public boolean onTouchEvent(MotionEvent motionEvent) {
|
||||
final float x = motionEvent.getRawX();
|
||||
final float y = motionEvent.getRawY();
|
||||
switch (motionEvent.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mDownX = x;
|
||||
mDownY = y;
|
||||
mControllers.taskbarStashController.startStashHint(/* animateForward = */ true);
|
||||
mCanceledStashHint = false;
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (!mCanceledStashHint
|
||||
&& squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop) {
|
||||
mControllers.taskbarStashController.startStashHint(
|
||||
/* animateForward= */ false);
|
||||
mCanceledStashHint = true;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
if (!mCanceledStashHint) {
|
||||
mControllers.taskbarStashController.startStashHint(
|
||||
/* animateForward= */ false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies launcher to update icon alignment.
|
||||
*/
|
||||
public void notifyIconLayoutBoundsChanged() {
|
||||
mControllers.uiController.onIconLayoutBoundsChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the taskbar scrim when the visibility of taskbar changes.
|
||||
*/
|
||||
public void notifyVisibilityChanged() {
|
||||
mControllers.taskbarScrimViewController.onTaskbarVisibilityChanged(
|
||||
mTaskbarView.getVisibility());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,8 @@ public final class TaskbarAllAppsController {
|
||||
mAppsModelFlags = flags;
|
||||
mPackageUserKeytoUidMap = map;
|
||||
if (mAppsView != null) {
|
||||
mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap);
|
||||
mAppsView.getAppsStore().setApps(
|
||||
mApps, mAppsModelFlags, mPackageUserKeytoUidMap, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,10 +129,19 @@ public final class TaskbarAllAppsController {
|
||||
|
||||
/** Toggles visibility of {@link TaskbarAllAppsContainerView} in the overlay window. */
|
||||
public void toggle() {
|
||||
toggle(false);
|
||||
}
|
||||
|
||||
/** Toggles visibility of {@link TaskbarAllAppsContainerView} with the keyboard for search. */
|
||||
public void toggleSearch() {
|
||||
toggle(true);
|
||||
}
|
||||
|
||||
private void toggle(boolean showKeyboard) {
|
||||
if (isOpen()) {
|
||||
mSlideInView.close(true);
|
||||
} else {
|
||||
show(true);
|
||||
show(true, showKeyboard);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,13 +151,13 @@ public final class TaskbarAllAppsController {
|
||||
}
|
||||
|
||||
private void show(boolean animate) {
|
||||
show(animate, false);
|
||||
}
|
||||
|
||||
private void show(boolean animate, boolean showKeyboard) {
|
||||
if (mAppsView != null) {
|
||||
return;
|
||||
}
|
||||
// mControllers and getSharedState should never be null here. Do not handle null-pointer
|
||||
// to catch invalid states.
|
||||
mControllers.getSharedState().allAppsVisible = true;
|
||||
|
||||
mOverlayContext = mControllers.taskbarOverlayController.requestWindow();
|
||||
|
||||
// Initialize search session for All Apps.
|
||||
@@ -161,16 +171,20 @@ public final class TaskbarAllAppsController {
|
||||
|
||||
mSlideInView = (TaskbarAllAppsSlideInView) mOverlayContext.getLayoutInflater().inflate(
|
||||
R.layout.taskbar_all_apps_sheet, mOverlayContext.getDragLayer(), false);
|
||||
mSlideInView.addOnCloseListener(() -> {
|
||||
mControllers.getSharedState().allAppsVisible = false;
|
||||
cleanUpOverlay();
|
||||
});
|
||||
// Ensures All Apps gets touch events in case it is not the top floating view. Floating
|
||||
// views above it may not be able to intercept the touch, so All Apps should try to.
|
||||
mOverlayContext.getDragLayer().addTouchController(mSlideInView);
|
||||
mSlideInView.addOnCloseListener(this::cleanUpOverlay);
|
||||
TaskbarAllAppsViewController viewController = new TaskbarAllAppsViewController(
|
||||
mOverlayContext, mSlideInView, mControllers, mSearchSessionController);
|
||||
mOverlayContext,
|
||||
mSlideInView,
|
||||
mControllers,
|
||||
mSearchSessionController,
|
||||
showKeyboard);
|
||||
|
||||
viewController.show(animate);
|
||||
mAppsView = mOverlayContext.getAppsView();
|
||||
mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap);
|
||||
mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap, false);
|
||||
mAppsView.getFloatingHeaderView()
|
||||
.findFixedRowByType(PredictionRowView.class)
|
||||
.setPredictedApps(mPredictedApps);
|
||||
@@ -195,6 +209,7 @@ public final class TaskbarAllAppsController {
|
||||
mSearchSessionController = null;
|
||||
}
|
||||
if (mOverlayContext != null) {
|
||||
mOverlayContext.getDragLayer().removeTouchController(mSlideInView);
|
||||
mOverlayContext.setSearchSessionController(null);
|
||||
mOverlayContext = null;
|
||||
}
|
||||
|
||||
@@ -220,7 +220,9 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
|
||||
@Override
|
||||
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
|
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
mNoIntercept = !mAppsView.shouldContainerScroll(ev);
|
||||
mNoIntercept = !mAppsView.shouldContainerScroll(ev)
|
||||
|| getTopOpenViewWithType(
|
||||
mActivityContext, TYPE_TOUCH_CONTROLLER_NO_INTERCEPT) != null;
|
||||
}
|
||||
return super.onControllerInterceptTouchEvent(ev);
|
||||
}
|
||||
|
||||
+23
-7
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -51,7 +51,11 @@ open class TaskbarSearchSessionController : ResourceBasedOverride, AllAppsTransi
|
||||
|
||||
open fun handleBackInvoked(): Boolean = false
|
||||
|
||||
open fun onAllAppsAnimationPending(animation: PendingAnimation, toAllApps: Boolean) = Unit
|
||||
open fun onAllAppsAnimationPending(
|
||||
animation: PendingAnimation,
|
||||
toAllApps: Boolean,
|
||||
showKeyboard: Boolean,
|
||||
) = Unit
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
|
||||
@@ -85,17 +85,31 @@ import java.util.concurrent.Executors;
|
||||
* information to render each of the bubbles & dispatches changes to
|
||||
* {@link BubbleBarViewController} which will then update {@link BubbleBarView} as needed.
|
||||
*
|
||||
* For details around the behavior of the bubble bar, see {@link BubbleBarView}.
|
||||
* <p>For details around the behavior of the bubble bar, see {@link BubbleBarView}.
|
||||
*/
|
||||
public class BubbleBarController extends IBubblesListener.Stub {
|
||||
|
||||
private static final String TAG = BubbleBarController.class.getSimpleName();
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
// Whether bubbles are showing in the bubble bar from launcher
|
||||
public static final boolean BUBBLE_BAR_ENABLED =
|
||||
/**
|
||||
* Determines whether bubbles can be shown in the bubble bar. This value updates when the
|
||||
* taskbar is recreated.
|
||||
*
|
||||
* @see #onTaskbarRecreated()
|
||||
*/
|
||||
private static boolean sBubbleBarEnabled =
|
||||
SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
|
||||
|
||||
/** Whether showing bubbles in the launcher bubble bar is enabled. */
|
||||
public static boolean isBubbleBarEnabled() {
|
||||
return sBubbleBarEnabled;
|
||||
}
|
||||
|
||||
/** Re-reads the value of the flag from SystemProperties when taskbar is recreated. */
|
||||
public static void onTaskbarRecreated() {
|
||||
sBubbleBarEnabled = SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
|
||||
}
|
||||
private static final int MASK_HIDE_BUBBLE_BAR = SYSUI_STATE_BOUNCER_SHOWING
|
||||
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
|
||||
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
|
||||
@@ -137,6 +151,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
private static class BubbleBarViewUpdate {
|
||||
boolean expandedChanged;
|
||||
boolean expanded;
|
||||
boolean shouldShowEducation;
|
||||
String selectedBubbleKey;
|
||||
String suppressedBubbleKey;
|
||||
String unsuppressedBubbleKey;
|
||||
@@ -151,6 +166,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
BubbleBarViewUpdate(BubbleBarUpdate update) {
|
||||
expandedChanged = update.expandedChanged;
|
||||
expanded = update.expanded;
|
||||
shouldShowEducation = update.shouldShowEducation;
|
||||
selectedBubbleKey = update.selectedBubbleKey;
|
||||
suppressedBubbleKey = update.suppressedBubbleKey;
|
||||
unsuppressedBubbleKey = update.unsupressedBubbleKey;
|
||||
@@ -165,7 +181,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
|
||||
mSystemUiProxy = SystemUiProxy.INSTANCE.get(context);
|
||||
|
||||
if (BUBBLE_BAR_ENABLED) {
|
||||
if (sBubbleBarEnabled) {
|
||||
mSystemUiProxy.setBubblesListener(this);
|
||||
}
|
||||
mMainExecutor = MAIN_EXECUTOR;
|
||||
@@ -188,8 +204,10 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
|
||||
|
||||
bubbleControllers.runAfterInit(() -> {
|
||||
mBubbleBarViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
|
||||
mBubbleStashedHandleViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
|
||||
mBubbleBarViewController.setHiddenForBubbles(
|
||||
!sBubbleBarEnabled || mBubbles.isEmpty());
|
||||
mBubbleStashedHandleViewController.setHiddenForBubbles(
|
||||
!sBubbleBarEnabled || mBubbles.isEmpty());
|
||||
mBubbleBarViewController.setUpdateSelectedBubbleAfterCollapse(
|
||||
key -> setSelectedBubble(mBubbles.get(key)));
|
||||
});
|
||||
@@ -366,7 +384,9 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
mBubbleStashController.animateToInitialState(update.expanded);
|
||||
}
|
||||
}
|
||||
|
||||
if (update.shouldShowEducation) {
|
||||
mBubbleBarViewController.prepareToShowEducation();
|
||||
}
|
||||
if (update.expandedChanged) {
|
||||
if (update.expanded != mBubbleBarViewController.isExpanded()) {
|
||||
mBubbleBarViewController.setExpandedFromSysui(update.expanded);
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.launcher3.taskbar.bubbles;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
@@ -74,7 +75,8 @@ public class BubbleBarViewController {
|
||||
// Whether the bar is hidden for a sysui state.
|
||||
private boolean mHiddenForSysui;
|
||||
// Whether the bar is hidden because there are no bubbles.
|
||||
private boolean mHiddenForNoBubbles;
|
||||
private boolean mHiddenForNoBubbles = true;
|
||||
private boolean mShouldShowEducation;
|
||||
|
||||
public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView) {
|
||||
mActivity = activity;
|
||||
@@ -98,7 +100,7 @@ public class BubbleBarViewController {
|
||||
mBarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarHeight;
|
||||
mBubbleBarScale.updateValue(1f);
|
||||
mBubbleClickListener = v -> onBubbleClicked(v);
|
||||
mBubbleBarClickListener = v -> setExpanded(true);
|
||||
mBubbleBarClickListener = v -> onBubbleBarClicked();
|
||||
mBubbleDragController.setupBubbleBarView(mBarView);
|
||||
mBarView.setOnClickListener(mBubbleBarClickListener);
|
||||
mBarView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) ->
|
||||
@@ -121,6 +123,21 @@ public class BubbleBarViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private void onBubbleBarClicked() {
|
||||
if (mShouldShowEducation) {
|
||||
mShouldShowEducation = false;
|
||||
// Get the bubble bar bounds on screen
|
||||
Rect bounds = new Rect();
|
||||
mBarView.getBoundsOnScreen(bounds);
|
||||
// Calculate user education reference position in Screen coordinates
|
||||
Point position = new Point(bounds.centerX(), bounds.top);
|
||||
// Show user education relative to the reference point
|
||||
mSystemUiProxy.showUserEducation(position);
|
||||
} else {
|
||||
setExpanded(true);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// The below animators are exposed to BubbleStashController so it can manage the stashing
|
||||
// animation.
|
||||
@@ -195,6 +212,7 @@ public class BubbleBarViewController {
|
||||
if (mHiddenForNoBubbles != hidden) {
|
||||
mHiddenForNoBubbles = hidden;
|
||||
updateVisibilityForStateChange();
|
||||
mActivity.bubbleBarVisibilityChanged(!hidden);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,6 +344,12 @@ public class BubbleBarViewController {
|
||||
}
|
||||
}
|
||||
|
||||
/** Marks as should show education and shows the bubble bar in a collapsed state */
|
||||
public void prepareToShowEducation() {
|
||||
mShouldShowEducation = true;
|
||||
mBubbleStashController.showBubbleBar(false /* expand the bubbles */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the dragged bubble view in the bubble bar view, and notifies SystemUI
|
||||
* that a bubble is being dragged to dismiss.
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.android.wm.shell.common.bubbles.DismissView
|
||||
fun DismissView.setup() {
|
||||
setup(
|
||||
DismissView.Config(
|
||||
dismissViewResId = R.id.dismiss_view,
|
||||
targetSizeResId = R.dimen.bubblebar_dismiss_target_size,
|
||||
iconSizeResId = R.dimen.bubblebar_dismiss_target_icon_size,
|
||||
bottomMarginResId = R.dimen.bubblebar_dismiss_target_bottom_margin,
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.annotation.Nullable;
|
||||
import android.view.InsetsController;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.taskbar.StashedHandleViewController;
|
||||
@@ -350,4 +351,9 @@ public class BubbleStashController {
|
||||
return mBubblesShowingOnHome ? getBubbleBarTranslationYForHotseat()
|
||||
: getBubbleBarTranslationYForTaskbar();
|
||||
}
|
||||
|
||||
/** Checks whether the motion event is over the stash handle. */
|
||||
public boolean isEventOverStashHandle(MotionEvent ev) {
|
||||
return mHandleViewController.isEventOverHandle(ev);
|
||||
}
|
||||
}
|
||||
|
||||
+19
@@ -24,6 +24,7 @@ import android.animation.ValueAnimator;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
|
||||
@@ -268,4 +269,22 @@ public class BubbleStashedHandleViewController {
|
||||
});
|
||||
return revealAnim;
|
||||
}
|
||||
|
||||
/** Checks that the stash handle is visible and that the motion event is within bounds. */
|
||||
public boolean isEventOverHandle(MotionEvent ev) {
|
||||
if (mStashedHandleView.getVisibility() != VISIBLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// the bounds of the handle only include the visible part, so we check that the Y coordinate
|
||||
// is anywhere within the stashed taskbar height.
|
||||
int top = mActivity.getDeviceProfile().heightPx - mStashedTaskbarHeight;
|
||||
|
||||
return (int) ev.getRawY() >= top && containsX((int) ev.getRawX());
|
||||
}
|
||||
|
||||
/** Checks if the given x coordinate is within the stashed handle bounds. */
|
||||
public boolean containsX(int x) {
|
||||
return x >= mStashedHandleBounds.left && x <= mStashedHandleBounds.right;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +163,7 @@ public class BubbleView extends ConstraintLayout {
|
||||
mBubble = overflow;
|
||||
mBubbleIcon.setImageBitmap(bitmap);
|
||||
mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
|
||||
setContentDescription(getResources().getString(R.string.bubble_bar_overflow_description));
|
||||
}
|
||||
|
||||
/** Returns the bubble being rendered in this view. */
|
||||
|
||||
+31
-4
@@ -18,12 +18,15 @@ package com.android.launcher3.taskbar.navbutton
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.drawable.RotateDrawable
|
||||
import android.view.Gravity
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.Utilities
|
||||
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
|
||||
import com.android.systemui.shared.rotation.RotationButton
|
||||
|
||||
/**
|
||||
* Meant to be a simple container for data subclasses will need
|
||||
@@ -37,10 +40,13 @@ import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonL
|
||||
* @property startContextualContainer ViewGroup that holds the start contextual button (ex, A11y).
|
||||
*/
|
||||
abstract class AbstractNavButtonLayoutter(
|
||||
val resources: Resources,
|
||||
val navButtonContainer: LinearLayout,
|
||||
protected val endContextualContainer: ViewGroup,
|
||||
protected val startContextualContainer: ViewGroup
|
||||
val resources: Resources,
|
||||
val navButtonContainer: LinearLayout,
|
||||
protected val endContextualContainer: ViewGroup,
|
||||
protected val startContextualContainer: ViewGroup,
|
||||
protected val imeSwitcher: ImageView?,
|
||||
protected val rotationButton: RotationButton?,
|
||||
protected val a11yButton: ImageView?
|
||||
) : NavButtonLayoutter {
|
||||
protected val homeButton: ImageView? = navButtonContainer.findViewById(R.id.home)
|
||||
protected val recentsButton: ImageView? = navButtonContainer.findViewById(R.id.recent_apps)
|
||||
@@ -56,4 +62,25 @@ abstract class AbstractNavButtonLayoutter(
|
||||
backButton.setImageDrawable(rotateDrawable)
|
||||
}
|
||||
}
|
||||
|
||||
fun getParamsToCenterView(): FrameLayout.LayoutParams {
|
||||
val params = FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
params.gravity = Gravity.CENTER
|
||||
return params;
|
||||
}
|
||||
|
||||
open fun repositionContextualContainer(contextualContainer: ViewGroup, barAxisMargin: Int,
|
||||
gravity: Int) {
|
||||
val contextualContainerParams = FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||
contextualContainerParams.apply {
|
||||
marginStart = barAxisMargin
|
||||
marginEnd = barAxisMargin
|
||||
topMargin = 0
|
||||
bottomMargin = 0
|
||||
}
|
||||
contextualContainerParams.gravity = gravity or Gravity.CENTER_VERTICAL
|
||||
contextualContainer.layoutParams = contextualContainerParams
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,22 +25,30 @@ import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import com.android.launcher3.DeviceProfile
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.*
|
||||
import com.android.systemui.shared.rotation.RotationButton
|
||||
|
||||
class KidsNavLayoutter(
|
||||
resources: Resources,
|
||||
navBarContainer: LinearLayout,
|
||||
endContextualContainer: ViewGroup,
|
||||
startContextualContainer: ViewGroup
|
||||
resources: Resources,
|
||||
navBarContainer: LinearLayout,
|
||||
endContextualContainer: ViewGroup,
|
||||
startContextualContainer: ViewGroup,
|
||||
imeSwitcher: ImageView?,
|
||||
rotationButton: RotationButton?,
|
||||
a11yButton: ImageView?
|
||||
) :
|
||||
AbstractNavButtonLayoutter(
|
||||
resources,
|
||||
navBarContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
resources,
|
||||
navBarContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
rotationButton,
|
||||
a11yButton
|
||||
) {
|
||||
|
||||
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
|
||||
override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
|
||||
val iconSize: Int = resources.getDimensionPixelSize(DIMEN_TASKBAR_ICON_SIZE_KIDS)
|
||||
val buttonWidth: Int = resources.getDimensionPixelSize(DIMEN_TASKBAR_NAV_BUTTONS_WIDTH_KIDS)
|
||||
val buttonHeight: Int =
|
||||
@@ -89,5 +97,25 @@ class KidsNavLayoutter(
|
||||
navButtonContainer.requestLayout()
|
||||
|
||||
homeButton.onLongClickListener = null
|
||||
|
||||
endContextualContainer.removeAllViews()
|
||||
startContextualContainer.removeAllViews()
|
||||
|
||||
val contextualMargin = resources.getDimensionPixelSize(
|
||||
R.dimen.taskbar_contextual_button_padding)
|
||||
repositionContextualContainer(endContextualContainer, 0, Gravity.END)
|
||||
repositionContextualContainer(startContextualContainer, contextualMargin, Gravity.START)
|
||||
|
||||
if (imeSwitcher != null) {
|
||||
startContextualContainer.addView(imeSwitcher)
|
||||
imeSwitcher.layoutParams = getParamsToCenterView()
|
||||
}
|
||||
if (a11yButton != null) {
|
||||
endContextualContainer.addView(a11yButton)
|
||||
}
|
||||
if (rotationButton != null) {
|
||||
endContextualContainer.addView(rotationButton.currentView)
|
||||
rotationButton.currentView.layoutParams = getParamsToCenterView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,13 @@ import android.view.Surface.ROTATION_90
|
||||
import android.view.Surface.Rotation
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import com.android.launcher3.DeviceProfile
|
||||
import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.*
|
||||
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.Companion
|
||||
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
|
||||
import com.android.systemui.shared.rotation.RotationButton
|
||||
|
||||
/**
|
||||
* Select the correct layout for nav buttons
|
||||
@@ -52,14 +54,17 @@ class NavButtonLayoutFactory {
|
||||
* @param isThreeButtonNav are no-ops when taskbar is present/showing
|
||||
*/
|
||||
fun getUiLayoutter(
|
||||
deviceProfile: DeviceProfile,
|
||||
navButtonsView: FrameLayout,
|
||||
resources: Resources,
|
||||
isKidsMode: Boolean,
|
||||
isInSetup: Boolean,
|
||||
isThreeButtonNav: Boolean,
|
||||
phoneMode: Boolean,
|
||||
@Rotation surfaceRotation: Int
|
||||
deviceProfile: DeviceProfile,
|
||||
navButtonsView: FrameLayout,
|
||||
imeSwitcher: ImageView?,
|
||||
rotationButton: RotationButton?,
|
||||
a11yButton: ImageView?,
|
||||
resources: Resources,
|
||||
isKidsMode: Boolean,
|
||||
isInSetup: Boolean,
|
||||
isThreeButtonNav: Boolean,
|
||||
phoneMode: Boolean,
|
||||
@Rotation surfaceRotation: Int
|
||||
): NavButtonLayoutter {
|
||||
val navButtonContainer =
|
||||
navButtonsView.requireViewById<LinearLayout>(ID_END_NAV_BUTTONS)
|
||||
@@ -73,24 +78,33 @@ class NavButtonLayoutFactory {
|
||||
isPhoneNavMode -> {
|
||||
if (!deviceProfile.isLandscape) {
|
||||
PhonePortraitNavLayoutter(
|
||||
resources,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
resources,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
rotationButton,
|
||||
a11yButton
|
||||
)
|
||||
} else if (surfaceRotation == ROTATION_90) {
|
||||
PhoneLandscapeNavLayoutter(
|
||||
resources,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
resources,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
rotationButton,
|
||||
a11yButton
|
||||
)
|
||||
} else {
|
||||
PhoneSeascapeNavLayoutter(
|
||||
resources,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
rotationButton,
|
||||
a11yButton
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -99,33 +113,45 @@ class NavButtonLayoutFactory {
|
||||
resources,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
rotationButton,
|
||||
a11yButton
|
||||
)
|
||||
}
|
||||
deviceProfile.isTaskbarPresent -> {
|
||||
return when {
|
||||
isInSetup -> {
|
||||
SetupNavLayoutter(
|
||||
resources,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
resources,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
rotationButton,
|
||||
a11yButton
|
||||
)
|
||||
}
|
||||
isKidsMode -> {
|
||||
KidsNavLayoutter(
|
||||
resources,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
resources,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
rotationButton,
|
||||
a11yButton
|
||||
)
|
||||
}
|
||||
else ->
|
||||
TaskbarNavLayoutter(
|
||||
resources,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
resources,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
rotationButton,
|
||||
a11yButton
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -136,6 +162,6 @@ class NavButtonLayoutFactory {
|
||||
|
||||
/** Lays out and provides access to the home, recents, and back buttons for various mischief */
|
||||
interface NavButtonLayoutter {
|
||||
fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean)
|
||||
fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,24 +18,33 @@ package com.android.launcher3.taskbar.navbutton
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import com.android.launcher3.DeviceProfile
|
||||
import com.android.systemui.shared.rotation.RotationButton
|
||||
|
||||
/** Layoutter for showing gesture navigation on phone screen. No buttons here, no-op container */
|
||||
class PhoneGestureLayoutter(
|
||||
resources: Resources,
|
||||
navBarContainer: LinearLayout,
|
||||
endContextualContainer: ViewGroup,
|
||||
startContextualContainer: ViewGroup
|
||||
startContextualContainer: ViewGroup,
|
||||
imeSwitcher: ImageView?,
|
||||
rotationButton: RotationButton?,
|
||||
a11yButton: ImageView?
|
||||
) :
|
||||
AbstractNavButtonLayoutter(
|
||||
resources,
|
||||
navBarContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
rotationButton,
|
||||
a11yButton
|
||||
) {
|
||||
|
||||
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
|
||||
// no-op
|
||||
override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
|
||||
endContextualContainer.removeAllViews()
|
||||
startContextualContainer.removeAllViews()
|
||||
}
|
||||
}
|
||||
|
||||
+63
-14
@@ -20,27 +20,35 @@ import android.content.res.Resources
|
||||
import android.view.Gravity
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.view.children
|
||||
import com.android.launcher3.DeviceProfile
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.taskbar.TaskbarManager
|
||||
import com.android.launcher3.util.DimensionUtils
|
||||
import com.android.systemui.shared.rotation.RotationButton
|
||||
|
||||
open class PhoneLandscapeNavLayoutter(
|
||||
resources: Resources,
|
||||
navBarContainer: LinearLayout,
|
||||
endContextualContainer: ViewGroup,
|
||||
startContextualContainer: ViewGroup
|
||||
resources: Resources,
|
||||
navBarContainer: LinearLayout,
|
||||
endContextualContainer: ViewGroup,
|
||||
startContextualContainer: ViewGroup,
|
||||
imeSwitcher: ImageView?,
|
||||
rotationButton: RotationButton?,
|
||||
a11yButton: ImageView?,
|
||||
) :
|
||||
AbstractNavButtonLayoutter(
|
||||
resources,
|
||||
navBarContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
resources,
|
||||
navBarContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
rotationButton,
|
||||
a11yButton
|
||||
) {
|
||||
|
||||
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
|
||||
override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
|
||||
// TODO(b/230395757): Polish pending, this is just to make it usable
|
||||
val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
|
||||
val taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
|
||||
@@ -57,14 +65,11 @@ open class PhoneLandscapeNavLayoutter(
|
||||
marginStart = 0
|
||||
}
|
||||
|
||||
// Swap recents and back button
|
||||
navButtonContainer.addView(recentsButton)
|
||||
navButtonContainer.addView(homeButton)
|
||||
navButtonContainer.addView(backButton)
|
||||
|
||||
navButtonContainer.layoutParams = navContainerParams
|
||||
navButtonContainer.gravity = Gravity.CENTER
|
||||
|
||||
addThreeButtons()
|
||||
|
||||
// Add the spaces in between the nav buttons
|
||||
val spaceInBetween: Int =
|
||||
resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone)
|
||||
@@ -84,5 +89,49 @@ open class PhoneLandscapeNavLayoutter(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositionContextualButtons()
|
||||
}
|
||||
|
||||
open fun addThreeButtons() {
|
||||
// Swap recents and back button
|
||||
navButtonContainer.addView(recentsButton)
|
||||
navButtonContainer.addView(homeButton)
|
||||
navButtonContainer.addView(backButton)
|
||||
}
|
||||
|
||||
open fun repositionContextualButtons() {
|
||||
endContextualContainer.removeAllViews()
|
||||
startContextualContainer.removeAllViews()
|
||||
|
||||
val contextualMargin = resources.getDimensionPixelSize(
|
||||
R.dimen.taskbar_contextual_button_padding)
|
||||
repositionContextualContainer(startContextualContainer, contextualMargin, Gravity.TOP)
|
||||
|
||||
if (imeSwitcher != null) {
|
||||
startContextualContainer.addView(imeSwitcher)
|
||||
imeSwitcher.layoutParams = getParamsToCenterView()
|
||||
}
|
||||
if (a11yButton != null) {
|
||||
startContextualContainer.addView(a11yButton)
|
||||
}
|
||||
if (rotationButton != null) {
|
||||
startContextualContainer.addView(rotationButton.currentView)
|
||||
rotationButton.currentView.layoutParams = getParamsToCenterView()
|
||||
}
|
||||
}
|
||||
|
||||
override fun repositionContextualContainer(contextualContainer: ViewGroup, barAxisMargin: Int,
|
||||
gravity: Int) {
|
||||
val contextualContainerParams = FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
contextualContainerParams.apply {
|
||||
marginStart = 0
|
||||
marginEnd = 0
|
||||
topMargin = barAxisMargin
|
||||
bottomMargin = barAxisMargin
|
||||
}
|
||||
contextualContainerParams.gravity = gravity or Gravity.CENTER_HORIZONTAL
|
||||
contextualContainer.layoutParams = contextualContainerParams
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,26 +20,34 @@ import android.content.res.Resources
|
||||
import android.view.Gravity
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import com.android.launcher3.DeviceProfile
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.taskbar.TaskbarManager
|
||||
import com.android.launcher3.util.DimensionUtils
|
||||
import com.android.systemui.shared.rotation.RotationButton
|
||||
|
||||
class PhonePortraitNavLayoutter(
|
||||
resources: Resources,
|
||||
navBarContainer: LinearLayout,
|
||||
endContextualContainer: ViewGroup,
|
||||
startContextualContainer: ViewGroup
|
||||
resources: Resources,
|
||||
navBarContainer: LinearLayout,
|
||||
endContextualContainer: ViewGroup,
|
||||
startContextualContainer: ViewGroup,
|
||||
imeSwitcher: ImageView?,
|
||||
rotationButton: RotationButton?,
|
||||
a11yButton: ImageView?,
|
||||
) :
|
||||
AbstractNavButtonLayoutter(
|
||||
resources,
|
||||
navBarContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
resources,
|
||||
navBarContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
rotationButton,
|
||||
a11yButton
|
||||
) {
|
||||
|
||||
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
|
||||
override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
|
||||
// TODO(b/230395757): Polish pending, this is just to make it usable
|
||||
val taskbarDimensions =
|
||||
DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
|
||||
@@ -90,5 +98,24 @@ class PhonePortraitNavLayoutter(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
endContextualContainer.removeAllViews()
|
||||
startContextualContainer.removeAllViews()
|
||||
|
||||
val contextualMargin = resources.getDimensionPixelSize(
|
||||
R.dimen.taskbar_contextual_button_padding)
|
||||
repositionContextualContainer(endContextualContainer, contextualMargin, Gravity.END)
|
||||
|
||||
if (imeSwitcher != null) {
|
||||
endContextualContainer.addView(imeSwitcher)
|
||||
imeSwitcher.layoutParams = getParamsToCenterView()
|
||||
}
|
||||
if (a11yButton != null) {
|
||||
endContextualContainer.addView(a11yButton)
|
||||
}
|
||||
if (rotationButton != null) {
|
||||
endContextualContainer.addView(rotationButton.currentView)
|
||||
rotationButton.currentView.layoutParams = getParamsToCenterView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,30 +17,57 @@
|
||||
package com.android.launcher3.taskbar.navbutton
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.view.Gravity
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import com.android.launcher3.DeviceProfile
|
||||
import com.android.launcher3.R
|
||||
import com.android.systemui.shared.rotation.RotationButton
|
||||
|
||||
class PhoneSeascapeNavLayoutter(
|
||||
resources: Resources,
|
||||
navBarContainer: LinearLayout,
|
||||
endContextualContainer: ViewGroup,
|
||||
startContextualContainer: ViewGroup
|
||||
startContextualContainer: ViewGroup,
|
||||
imeSwitcher: ImageView?,
|
||||
rotationButton: RotationButton?,
|
||||
a11yButton: ImageView?
|
||||
) :
|
||||
PhoneLandscapeNavLayoutter(
|
||||
resources,
|
||||
navBarContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
rotationButton,
|
||||
a11yButton
|
||||
) {
|
||||
|
||||
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
|
||||
// TODO(b/230395757): Polish pending, this is just to make it usable
|
||||
super.layoutButtons(dp, isContextualButtonShowing)
|
||||
navButtonContainer.removeAllViews()
|
||||
override fun addThreeButtons() {
|
||||
// Flip ordering of back and recents buttons
|
||||
navButtonContainer.addView(backButton)
|
||||
navButtonContainer.addView(homeButton)
|
||||
navButtonContainer.addView(recentsButton)
|
||||
}
|
||||
|
||||
override fun repositionContextualButtons() {
|
||||
endContextualContainer.removeAllViews()
|
||||
startContextualContainer.removeAllViews()
|
||||
|
||||
val contextualMargin = resources.getDimensionPixelSize(
|
||||
R.dimen.taskbar_contextual_button_padding)
|
||||
repositionContextualContainer(endContextualContainer, contextualMargin, Gravity.BOTTOM)
|
||||
|
||||
if (imeSwitcher != null) {
|
||||
endContextualContainer.addView(imeSwitcher)
|
||||
imeSwitcher.layoutParams = getParamsToCenterView()
|
||||
}
|
||||
if (a11yButton != null) {
|
||||
endContextualContainer.addView(a11yButton)
|
||||
}
|
||||
if (rotationButton != null) {
|
||||
endContextualContainer.addView(rotationButton.currentView)
|
||||
rotationButton.currentView.layoutParams = getParamsToCenterView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,23 +20,32 @@ import android.content.res.Resources
|
||||
import android.view.Gravity
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import com.android.launcher3.DeviceProfile
|
||||
import com.android.launcher3.R
|
||||
import com.android.systemui.shared.rotation.RotationButton
|
||||
|
||||
class SetupNavLayoutter(
|
||||
resources: Resources,
|
||||
navButtonContainer: LinearLayout,
|
||||
endContextualContainer: ViewGroup,
|
||||
startContextualContainer: ViewGroup
|
||||
resources: Resources,
|
||||
navButtonContainer: LinearLayout,
|
||||
endContextualContainer: ViewGroup,
|
||||
startContextualContainer: ViewGroup,
|
||||
imeSwitcher: ImageView?,
|
||||
rotationButton: RotationButton?,
|
||||
a11yButton: ImageView?
|
||||
) :
|
||||
AbstractNavButtonLayoutter(
|
||||
resources,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
resources,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
rotationButton,
|
||||
a11yButton
|
||||
) {
|
||||
|
||||
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
|
||||
override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
|
||||
// Since setup wizard only has back button enabled, it looks strange to be
|
||||
// end-aligned, so start-align instead.
|
||||
val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
|
||||
@@ -46,5 +55,25 @@ class SetupNavLayoutter(
|
||||
gravity = Gravity.START
|
||||
}
|
||||
navButtonContainer.requestLayout()
|
||||
|
||||
endContextualContainer.removeAllViews()
|
||||
startContextualContainer.removeAllViews()
|
||||
|
||||
val contextualMargin = resources.getDimensionPixelSize(
|
||||
R.dimen.taskbar_contextual_button_padding)
|
||||
repositionContextualContainer(endContextualContainer, 0, Gravity.END)
|
||||
repositionContextualContainer(startContextualContainer, contextualMargin, Gravity.START)
|
||||
|
||||
if (imeSwitcher != null) {
|
||||
startContextualContainer.addView(imeSwitcher)
|
||||
imeSwitcher.layoutParams = getParamsToCenterView()
|
||||
}
|
||||
if (a11yButton != null) {
|
||||
endContextualContainer.addView(a11yButton)
|
||||
}
|
||||
if (rotationButton != null) {
|
||||
endContextualContainer.addView(rotationButton.currentView)
|
||||
rotationButton.currentView.layoutParams = getParamsToCenterView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,31 +20,41 @@ import android.content.res.Resources
|
||||
import android.view.Gravity
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import com.android.launcher3.DeviceProfile
|
||||
import com.android.launcher3.R
|
||||
import com.android.systemui.shared.rotation.RotationButton
|
||||
|
||||
/** Layoutter for showing 3 button navigation on large screen */
|
||||
/**
|
||||
* Layoutter for rendering task bar in large screen, both in 3-button and gesture nav mode.
|
||||
*/
|
||||
class TaskbarNavLayoutter(
|
||||
resources: Resources,
|
||||
navBarContainer: LinearLayout,
|
||||
endContextualContainer: ViewGroup,
|
||||
startContextualContainer: ViewGroup
|
||||
resources: Resources,
|
||||
navBarContainer: LinearLayout,
|
||||
endContextualContainer: ViewGroup,
|
||||
startContextualContainer: ViewGroup,
|
||||
imeSwitcher: ImageView?,
|
||||
rotationButton: RotationButton?,
|
||||
a11yButton: ImageView?
|
||||
) :
|
||||
AbstractNavButtonLayoutter(
|
||||
resources,
|
||||
navBarContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer
|
||||
resources,
|
||||
navBarContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
rotationButton,
|
||||
a11yButton
|
||||
) {
|
||||
|
||||
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
|
||||
override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
|
||||
// Add spacing after the end of the last nav button
|
||||
var navMarginEnd = resources.getDimension(dp.inv.inlineNavButtonsEndSpacing).toInt()
|
||||
val contextualWidth = endContextualContainer.width
|
||||
// If contextual buttons are showing, we check if the end margin is enough for the
|
||||
// contextual button to be showing - if not, move the nav buttons over a smidge
|
||||
if (isContextualButtonShowing && navMarginEnd < contextualWidth) {
|
||||
if (isA11yButtonPersistent && navMarginEnd < contextualWidth) {
|
||||
// Additional spacing, eat up half of space between last icon and nav button
|
||||
navMarginEnd += resources.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2
|
||||
}
|
||||
@@ -77,5 +87,27 @@ class TaskbarNavLayoutter(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
endContextualContainer.removeAllViews()
|
||||
startContextualContainer.removeAllViews()
|
||||
|
||||
if (!dp.isGestureMode) {
|
||||
val contextualMargin = resources.getDimensionPixelSize(
|
||||
R.dimen.taskbar_contextual_button_padding)
|
||||
repositionContextualContainer(endContextualContainer, 0, Gravity.END)
|
||||
repositionContextualContainer(startContextualContainer, contextualMargin, Gravity.START)
|
||||
|
||||
if (imeSwitcher != null) {
|
||||
startContextualContainer.addView(imeSwitcher)
|
||||
imeSwitcher.layoutParams = getParamsToCenterView()
|
||||
}
|
||||
if (a11yButton != null) {
|
||||
endContextualContainer.addView(a11yButton)
|
||||
}
|
||||
if (rotationButton != null) {
|
||||
endContextualContainer.addView(rotationButton.currentView)
|
||||
rotationButton.currentView.layoutParams = getParamsToCenterView()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.android.launcher3.taskbar.overlay;
|
||||
|
||||
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
|
||||
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS;
|
||||
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
|
||||
@@ -23,7 +24,6 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
|
||||
import static com.android.launcher3.LauncherState.ALL_APPS;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.view.Gravity;
|
||||
@@ -60,14 +60,26 @@ public final class TaskbarOverlayController {
|
||||
|
||||
private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
|
||||
@Override
|
||||
public void onTaskCreated(int taskId, ComponentName componentName) {
|
||||
// Created task will be below existing overlay, so move out of the way.
|
||||
hideWindow();
|
||||
public void onTaskMovedToFront(int taskId) {
|
||||
// New front task will be below existing overlay, so move out of the way.
|
||||
hideWindowOnTaskStackChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskMovedToFront(int taskId) {
|
||||
// New front task will be below existing overlay, so move out of the way.
|
||||
public void onTaskStackChanged() {
|
||||
// The other callbacks are insufficient for All Apps, because there are many cases where
|
||||
// it can relaunch the same task already behind it. However, this callback needs to be a
|
||||
// no-op when only EDU is shown, because going between the EDU steps invokes this
|
||||
// callback.
|
||||
if (mControllers.getSharedState() != null
|
||||
&& mControllers.getSharedState().allAppsVisible) {
|
||||
hideWindowOnTaskStackChange();
|
||||
}
|
||||
}
|
||||
|
||||
private void hideWindowOnTaskStackChange() {
|
||||
// A task was launched while overlay window was open, so stash Taskbar.
|
||||
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
|
||||
hideWindow();
|
||||
}
|
||||
};
|
||||
@@ -176,6 +188,7 @@ public final class TaskbarOverlayController {
|
||||
layoutParams.setFitInsetsTypes(0); // Handled by container view.
|
||||
layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
|
||||
layoutParams.setSystemApplicationOverlay(true);
|
||||
layoutParams.privateFlags = PRIVATE_FLAG_CONSUME_IME_INSETS;
|
||||
return layoutParams;
|
||||
}
|
||||
|
||||
@@ -199,8 +212,10 @@ public final class TaskbarOverlayController {
|
||||
|
||||
@Override
|
||||
protected void handleClose(boolean animate) {
|
||||
mTaskbarContext.getDragLayer().removeView(this);
|
||||
Optional.ofNullable(mOverlayContext).ifPresent(c -> closeAllOpenViews(c, animate));
|
||||
if (mIsOpen) {
|
||||
mTaskbarContext.getDragLayer().removeView(this);
|
||||
Optional.ofNullable(mOverlayContext).ifPresent(c -> closeAllOpenViews(c, animate));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -38,6 +38,7 @@ import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
@@ -69,6 +70,7 @@ public class TaskbarOverlayDragLayer extends
|
||||
return true;
|
||||
}
|
||||
};
|
||||
private final List<TouchController> mTouchControllers = new ArrayList<>();
|
||||
|
||||
TaskbarOverlayDragLayer(Context context) {
|
||||
super(context, null, 1);
|
||||
@@ -93,10 +95,13 @@ public class TaskbarOverlayDragLayer extends
|
||||
|
||||
@Override
|
||||
public void recreateControllers() {
|
||||
mControllers = mOnClickListeners.isEmpty()
|
||||
? new TouchController[]{mActivity.getDragController()}
|
||||
: new TouchController[] {
|
||||
mActivity.getDragController(), mClickListenerTouchController};
|
||||
List<TouchController> controllers = new ArrayList<>();
|
||||
controllers.add(mActivity.getDragController());
|
||||
controllers.addAll(mTouchControllers);
|
||||
if (!mOnClickListeners.isEmpty()) {
|
||||
controllers.add(mClickListenerTouchController);
|
||||
}
|
||||
mControllers = controllers.toArray(new TouchController[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -113,6 +118,15 @@ public class TaskbarOverlayDragLayer extends
|
||||
topView.onBackInvoked();
|
||||
return true;
|
||||
}
|
||||
} else if (event.getAction() == KeyEvent.ACTION_DOWN
|
||||
&& event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE && event.hasNoModifiers()) {
|
||||
// Ignore escape if pressed in conjunction with any modifier keys. Close each
|
||||
// floating view one at a time for each key press.
|
||||
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
|
||||
if (topView != null) {
|
||||
topView.close(/* animate= */ true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
@@ -183,6 +197,18 @@ public class TaskbarOverlayDragLayer extends
|
||||
});
|
||||
}
|
||||
|
||||
/** Adds a {@link TouchController} to this drag layer. */
|
||||
public void addTouchController(@NonNull TouchController touchController) {
|
||||
mTouchControllers.add(touchController);
|
||||
recreateControllers();
|
||||
}
|
||||
|
||||
/** Removes a {@link TouchController} from this drag layer. */
|
||||
public void removeTouchController(@NonNull TouchController touchController) {
|
||||
mTouchControllers.remove(touchController);
|
||||
recreateControllers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Taskbar automatically stashes when opening all apps, but we don't report the insets as
|
||||
* changing to avoid moving the underlying app. But internally, the apps view should still
|
||||
|
||||
@@ -21,12 +21,20 @@ import android.app.Person;
|
||||
import android.content.Context;
|
||||
import android.content.pm.LauncherActivityInfo;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.LauncherUserInfo;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.ArrayMap;
|
||||
import android.window.RemoteTransition;
|
||||
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.util.UserIconInfo;
|
||||
import com.android.quickstep.util.FadeOutRemoteTransition;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -53,4 +61,56 @@ public class ApiWrapper {
|
||||
options.setRemoteTransition(new RemoteTransition(new FadeOutRemoteTransition()));
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all users on the device to their corresponding UI properties
|
||||
*/
|
||||
public static Map<UserHandle, UserIconInfo> queryAllUsers(Context context) {
|
||||
UserManager um = context.getSystemService(UserManager.class);
|
||||
Map<UserHandle, UserIconInfo> users = new ArrayMap<>();
|
||||
List<UserHandle> usersActual = um.getUserProfiles();
|
||||
if (usersActual != null) {
|
||||
for (UserHandle user : usersActual) {
|
||||
if (android.os.Flags.allowPrivateProfile() && Flags.enablePrivateSpace()) {
|
||||
LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
|
||||
LauncherUserInfo launcherUserInfo = launcherApps.getLauncherUserInfo(user);
|
||||
// UserTypes not supported in Launcher are deemed to be the current
|
||||
// Foreground User.
|
||||
int userType = switch (launcherUserInfo.getUserType()) {
|
||||
case UserManager.USER_TYPE_PROFILE_MANAGED -> UserIconInfo.TYPE_WORK;
|
||||
case UserManager.USER_TYPE_PROFILE_CLONE -> UserIconInfo.TYPE_CLONED;
|
||||
case UserManager.USER_TYPE_PROFILE_PRIVATE -> UserIconInfo.TYPE_PRIVATE;
|
||||
default -> UserIconInfo.TYPE_MAIN;
|
||||
};
|
||||
long serial = launcherUserInfo.getUserSerialNumber();
|
||||
users.put(user, new UserIconInfo(user, userType, serial));
|
||||
} else {
|
||||
long serial = um.getSerialNumberForUser(user);
|
||||
|
||||
// Simple check to check if the provided user is work profile
|
||||
// TODO: Migrate to a better platform API
|
||||
NoopDrawable d = new NoopDrawable();
|
||||
boolean isWork = (d != context.getPackageManager().getUserBadgedIcon(d, user));
|
||||
UserIconInfo info = new UserIconInfo(
|
||||
user,
|
||||
isWork ? UserIconInfo.TYPE_WORK : UserIconInfo.TYPE_MAIN,
|
||||
serial);
|
||||
users.put(user, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
private static class NoopDrawable extends ColorDrawable {
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,8 +107,7 @@ public abstract class BaseRecentsViewStateController<T extends RecentsView>
|
||||
setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
|
||||
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
|
||||
|
||||
boolean exitingOverview = !FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
|
||||
&& !toState.overviewUi;
|
||||
boolean exitingOverview = !FeatureFlags.enableSplitContextually() && !toState.overviewUi;
|
||||
if (mRecentsView.isSplitSelectionActive() && exitingOverview) {
|
||||
setter.add(mRecentsView.getSplitSelectController().getSplitAnimationController()
|
||||
.createPlaceholderDismissAnim(mLauncher));
|
||||
|
||||
@@ -44,13 +44,13 @@ import android.view.ViewGroup;
|
||||
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
|
||||
import com.android.launcher3.CellLayout;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
|
||||
import com.android.launcher3.celllayout.DelegatedCellDrawing;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.launcher3.icons.GraphicsUtils;
|
||||
import com.android.launcher3.icons.IconNormalizer;
|
||||
@@ -418,7 +418,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
/**
|
||||
* Draws Predicted Icon outline on cell layout
|
||||
*/
|
||||
public static class PredictedIconOutlineDrawing extends CellLayout.DelegatedCellDrawing {
|
||||
public static class PredictedIconOutlineDrawing extends DelegatedCellDrawing {
|
||||
|
||||
private final PredictedAppIcon mIcon;
|
||||
private final Paint mOutlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
@@ -27,8 +27,10 @@ import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.widget.RemoteViews;
|
||||
import android.widget.Toast;
|
||||
import android.window.SplashScreen;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.logging.StatsLogManager;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
@@ -56,8 +58,9 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
|
||||
return RemoteViews.startPendingIntent(hostView, pendingIntent,
|
||||
remoteResponse.getLaunchOptions(view));
|
||||
}
|
||||
if (mLauncher.getSplitToWorkspaceController().handleSecondWidgetSelectionForSplit(view,
|
||||
pendingIntent)) {
|
||||
if (mLauncher.isSplitSelectionEnabled()) {
|
||||
Toast.makeText(hostView.getContext(), R.string.split_widgets_not_supported,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
}
|
||||
Pair<Intent, ActivityOptions> options = remoteResponse.getLaunchOptions(view);
|
||||
|
||||
@@ -21,6 +21,8 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEAS
|
||||
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
|
||||
|
||||
import static com.android.app.animation.Interpolators.EMPHASIZED;
|
||||
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
|
||||
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
|
||||
import static com.android.launcher3.LauncherSettings.Animation.DEFAULT_NO_ICON;
|
||||
import static com.android.launcher3.LauncherSettings.Animation.VIEW_BACKGROUND;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
|
||||
@@ -33,7 +35,6 @@ import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
|
||||
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
|
||||
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
|
||||
import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
|
||||
@@ -50,9 +51,10 @@ import static com.android.launcher3.testing.shared.TestProtocol.QUICK_SWITCH_STA
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
|
||||
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
|
||||
import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
|
||||
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
|
||||
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
|
||||
import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
@@ -61,7 +63,6 @@ import android.app.ActivityOptions;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
@@ -93,6 +94,7 @@ import androidx.annotation.RequiresApi;
|
||||
import com.android.app.viewcapture.SettingsAwareViewCapture;
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.HomeTransitionController;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.LauncherState;
|
||||
@@ -104,8 +106,10 @@ import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.apppairs.AppPairIcon;
|
||||
import com.android.launcher3.appprediction.PredictionRowView;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.desktop.DesktopRecentsTransitionController;
|
||||
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
|
||||
import com.android.launcher3.logging.InstanceId;
|
||||
import com.android.launcher3.logging.StatsLogManager;
|
||||
@@ -113,7 +117,6 @@ import com.android.launcher3.logging.StatsLogManager.StatsLogger;
|
||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||
import com.android.launcher3.model.WellbeingModel;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.proxy.ProxyActivityStarter;
|
||||
import com.android.launcher3.statehandlers.DepthController;
|
||||
@@ -163,7 +166,6 @@ import com.android.quickstep.util.SplitSelectStateController;
|
||||
import com.android.quickstep.util.SplitToWorkspaceController;
|
||||
import com.android.quickstep.util.SplitWithKeyboardShortcutController;
|
||||
import com.android.quickstep.util.TISBindHelper;
|
||||
import com.android.quickstep.views.DesktopTaskView;
|
||||
import com.android.quickstep.views.FloatingTaskView;
|
||||
import com.android.quickstep.views.OverviewActionsView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
@@ -185,6 +187,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
@@ -217,29 +220,40 @@ public class QuickstepLauncher extends Launcher {
|
||||
private SplitWithKeyboardShortcutController mSplitWithKeyboardShortcutController;
|
||||
private SplitToWorkspaceController mSplitToWorkspaceController;
|
||||
|
||||
private AsyncClockEventDelegate mAsyncClockEventDelegate;
|
||||
|
||||
/**
|
||||
* If Launcher restarted while in the middle of an Overview split select, it needs this data to
|
||||
* recover. In all other cases this will remain null.
|
||||
*/
|
||||
private PendingSplitSelectInfo mPendingSplitSelectInfo = null;
|
||||
|
||||
@Nullable
|
||||
private DesktopRecentsTransitionController mDesktopRecentsTransitionController;
|
||||
|
||||
private SafeCloseable mViewCapture;
|
||||
|
||||
private boolean mEnableWidgetDepth;
|
||||
|
||||
private HomeTransitionController mHomeTransitionController;
|
||||
|
||||
@Override
|
||||
protected void setupViews() {
|
||||
super.setupViews();
|
||||
|
||||
mActionsView = findViewById(R.id.overview_actions_view);
|
||||
RecentsView overviewPanel = getOverviewPanel();
|
||||
SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this);
|
||||
mSplitSelectStateController =
|
||||
new SplitSelectStateController(this, mHandler, getStateManager(),
|
||||
getDepthController(), getStatsLogManager(),
|
||||
SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this));
|
||||
overviewPanel.init(mActionsView, mSplitSelectStateController);
|
||||
systemUiProxy, RecentsModel.INSTANCE.get(this),
|
||||
() -> onStateBack());
|
||||
if (isDesktopModeSupported()) {
|
||||
mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
|
||||
getStateManager(), systemUiProxy, getIApplicationThread(),
|
||||
getDepthController());
|
||||
}
|
||||
overviewPanel.init(mActionsView, mSplitSelectStateController,
|
||||
mDesktopRecentsTransitionController);
|
||||
mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(this,
|
||||
mSplitSelectStateController);
|
||||
mSplitToWorkspaceController = new SplitToWorkspaceController(this,
|
||||
@@ -251,10 +265,15 @@ public class QuickstepLauncher extends Launcher {
|
||||
mAppTransitionManager.registerRemoteAnimations();
|
||||
mAppTransitionManager.registerRemoteTransitions();
|
||||
|
||||
if (FeatureFlags.enableHomeTransitionListener()) {
|
||||
mHomeTransitionController = new HomeTransitionController(this);
|
||||
mHomeTransitionController.registerHomeTransitionListener();
|
||||
}
|
||||
|
||||
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
|
||||
mDepthController = new DepthController(this);
|
||||
mDesktopVisibilityController = new DesktopVisibilityController(this);
|
||||
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
|
||||
if (isDesktopModeSupported()) {
|
||||
mDesktopVisibilityController.registerSystemUiListener();
|
||||
mSplitSelectStateController.initSplitFromDesktopController(this);
|
||||
}
|
||||
@@ -263,6 +282,7 @@ public class QuickstepLauncher extends Launcher {
|
||||
mEnableWidgetDepth = SystemProperties.getBoolean("ro.launcher.depth.widget", true);
|
||||
getWorkspace().addOverlayCallback(progress ->
|
||||
onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX));
|
||||
addBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -331,11 +351,6 @@ public class QuickstepLauncher extends Launcher {
|
||||
return new QuickstepTransitionManager(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QuickstepOnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
|
||||
return new QuickstepOnboardingPrefs(this, sharedPrefs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
@@ -362,8 +377,8 @@ public class QuickstepLauncher extends Launcher {
|
||||
}
|
||||
|
||||
if ((changeBits & ACTIVITY_STATE_RESUMED) != 0) {
|
||||
if (mTaskbarUIController != null) {
|
||||
mTaskbarUIController.onLauncherResumedOrPaused(hasBeenResumed());
|
||||
if (!FeatureFlags.enableHomeTransitionListener() && mTaskbarUIController != null) {
|
||||
mTaskbarUIController.onLauncherVisibilityChanged(hasBeenResumed());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,7 +479,11 @@ public class QuickstepLauncher extends Launcher {
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
mAppTransitionManager.onActivityDestroyed();
|
||||
if (mAppTransitionManager != null) {
|
||||
mAppTransitionManager.onActivityDestroyed();
|
||||
}
|
||||
mAppTransitionManager = null;
|
||||
|
||||
if (mUnfoldTransitionProgressProvider != null) {
|
||||
SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(null);
|
||||
mUnfoldTransitionProgressProvider.destroy();
|
||||
@@ -476,6 +495,10 @@ public class QuickstepLauncher extends Launcher {
|
||||
mLauncherUnfoldAnimationController.onDestroy();
|
||||
}
|
||||
|
||||
if (mHomeTransitionController != null) {
|
||||
mHomeTransitionController.unregisterHomeTransitionListener();
|
||||
}
|
||||
|
||||
if (mDesktopVisibilityController != null) {
|
||||
mDesktopVisibilityController.unregisterSystemUiListener();
|
||||
}
|
||||
@@ -484,14 +507,11 @@ public class QuickstepLauncher extends Launcher {
|
||||
mSplitSelectStateController.onDestroy();
|
||||
}
|
||||
|
||||
if (mAsyncClockEventDelegate != null) {
|
||||
mAsyncClockEventDelegate.onDestroy();
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
mHotseatPredictionController.destroy();
|
||||
mSplitWithKeyboardShortcutController.onDestroy();
|
||||
if (mViewCapture != null) mViewCapture.close();
|
||||
removeBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -610,6 +630,7 @@ public class QuickstepLauncher extends Launcher {
|
||||
mViewCapture = SettingsAwareViewCapture.getInstance(this).startCapture(getWindow());
|
||||
}
|
||||
getWindow().addPrivateFlags(PRIVATE_FLAG_OPTIMIZE_MEASURE);
|
||||
QuickstepOnboardingPrefs.setup(this);
|
||||
View.setTraceLayoutSteps(TRACE_LAYOUTS);
|
||||
View.setTracedRequestLayoutClassClass(TRACE_RELAYOUT_CLASS);
|
||||
}
|
||||
@@ -621,13 +642,14 @@ public class QuickstepLauncher extends Launcher {
|
||||
// using that.
|
||||
mSplitSelectStateController.findLastActiveTasksAndRunCallback(
|
||||
Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()),
|
||||
false /* findExactPairMatch */,
|
||||
foundTasks -> {
|
||||
@Nullable Task foundTask = foundTasks.get(0);
|
||||
boolean taskWasFound = foundTask != null;
|
||||
splitSelectSource.alreadyRunningTaskId = taskWasFound
|
||||
? foundTask.key.id
|
||||
: INVALID_TASK_ID;
|
||||
if (ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
|
||||
if (FeatureFlags.enableSplitContextually()) {
|
||||
startSplitToHome(splitSelectSource);
|
||||
} else {
|
||||
recentsView.initiateSplitSelect(splitSelectSource);
|
||||
@@ -661,6 +683,9 @@ public class QuickstepLauncher extends Launcher {
|
||||
floatingTaskView.setAlpha(1);
|
||||
floatingTaskView.addStagingAnimation(anim, startingTaskRect, tempRect,
|
||||
false /* fadeWithThumbnail */, true /* isStagedTask */);
|
||||
floatingTaskView.setOnClickListener(view ->
|
||||
mSplitSelectStateController.getSplitAnimationController().
|
||||
playAnimPlaceholderToFullscreen(this, view, Optional.empty()));
|
||||
mSplitSelectStateController.setFirstFloatingTaskView(floatingTaskView);
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
@@ -676,6 +701,17 @@ public class QuickstepLauncher extends Launcher {
|
||||
anim.buildAnim().start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSplitSelectionEnabled() {
|
||||
return mSplitSelectStateController.isSplitSelectActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) {
|
||||
if (mTaskbarUIController != null) {
|
||||
mTaskbarUIController.onStateTransitionCompletedAfterSwipeToHome(finalState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
@@ -693,6 +729,15 @@ public class QuickstepLauncher extends Launcher {
|
||||
}
|
||||
|
||||
super.onPause();
|
||||
|
||||
if (FeatureFlags.enableSplitContextually()) {
|
||||
// If Launcher pauses before both split apps are selected, exit split screen.
|
||||
if (!mSplitSelectStateController.isBothSplitAppsConfirmed() &&
|
||||
!mSplitSelectStateController.isLaunchingFirstAppFullscreen()) {
|
||||
mSplitSelectStateController.getSplitAnimationController()
|
||||
.playPlaceholderDismissAnim(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -738,15 +783,6 @@ public class QuickstepLauncher extends Launcher {
|
||||
() -> ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onScreenOnChanged(boolean isOn) {
|
||||
super.onScreenOnChanged(isOn);
|
||||
if (!isOn) {
|
||||
RecentsView recentsView = getOverviewPanel();
|
||||
recentsView.finishRecentsAnimation(true /* toRecents */, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllAppsTransition(float progress) {
|
||||
super.onAllAppsTransition(progress);
|
||||
@@ -866,7 +902,7 @@ public class QuickstepLauncher extends Launcher {
|
||||
|
||||
@Override
|
||||
public void setResumed() {
|
||||
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
|
||||
if (isDesktopModeSupported()) {
|
||||
DesktopVisibilityController controller = mDesktopVisibilityController;
|
||||
if (controller != null && controller.areFreeformTasksVisible()
|
||||
&& !controller.isRecentsGestureInProgress()) {
|
||||
@@ -971,6 +1007,13 @@ public class QuickstepLauncher extends Launcher {
|
||||
.playPlaceholderDismissAnim(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismissSplitSelection() {
|
||||
super.dismissSplitSelection();
|
||||
mSplitSelectStateController.getSplitAnimationController()
|
||||
.playPlaceholderDismissAnim(this);
|
||||
}
|
||||
|
||||
public <T extends OverviewActionsView> T getActionsView() {
|
||||
return (T) mActionsView;
|
||||
}
|
||||
@@ -1003,7 +1046,7 @@ public class QuickstepLauncher extends Launcher {
|
||||
|
||||
@Override
|
||||
public boolean supportsAdaptiveIconAnimation(View clickedView) {
|
||||
return mAppTransitionManager.hasControlRemoteAppTransitionPermission();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1038,10 +1081,7 @@ public class QuickstepLauncher extends Launcher {
|
||||
|
||||
@Override
|
||||
public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
|
||||
ActivityOptionsWrapper activityOptions =
|
||||
mAppTransitionManager.hasControlRemoteAppTransitionPermission()
|
||||
? mAppTransitionManager.getActivityLaunchOptions(v)
|
||||
: super.getActivityLaunchOptions(v, item);
|
||||
ActivityOptionsWrapper activityOptions = mAppTransitionManager.getActivityLaunchOptions(v);
|
||||
if (mLastTouchUpTime > 0) {
|
||||
activityOptions.options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER,
|
||||
mLastTouchUpTime);
|
||||
@@ -1155,11 +1195,6 @@ public class QuickstepLauncher extends Launcher {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tryClearAccessibilityFocus(View view) {
|
||||
view.clearAccessibilityFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
@@ -1235,36 +1270,29 @@ public class QuickstepLauncher extends Launcher {
|
||||
|
||||
/**
|
||||
* Launches the given {@link GroupTask} in splitscreen.
|
||||
*
|
||||
* If the second split task is missing, launches the first task normally.
|
||||
*/
|
||||
public void launchSplitTasks(@NonNull View taskView, @NonNull GroupTask groupTask) {
|
||||
if (groupTask.task2 == null) {
|
||||
UI_HELPER_EXECUTOR.execute(() ->
|
||||
ActivityManagerWrapper.getInstance().startActivityFromRecents(
|
||||
groupTask.task1.key,
|
||||
getActivityLaunchOptions(taskView, null).options));
|
||||
return;
|
||||
}
|
||||
public void launchSplitTasks(@NonNull GroupTask groupTask) {
|
||||
// Top/left and bottom/right tasks respectively.
|
||||
Task task1 = groupTask.task1;
|
||||
// task2 should never be null when calling this method. Allow a crash to catch invalid calls
|
||||
Task task2 = groupTask.task2;
|
||||
mSplitSelectStateController.launchExistingSplitPair(
|
||||
null /* launchingTaskView */,
|
||||
groupTask.task1.key.id,
|
||||
groupTask.task2.key.id,
|
||||
task1.key.id,
|
||||
task2.key.id,
|
||||
SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
|
||||
/* callback= */ success -> mSplitSelectStateController.resetState(),
|
||||
/* freezeTaskList= */ true,
|
||||
/* freezeTaskList= */ false,
|
||||
groupTask.mSplitBounds == null
|
||||
? DEFAULT_SPLIT_RATIO
|
||||
: groupTask.mSplitBounds.appsStackedVertically
|
||||
? groupTask.mSplitBounds.topTaskPercent
|
||||
: groupTask.mSplitBounds.leftTaskPercent);
|
||||
? SNAP_TO_50_50
|
||||
: groupTask.mSplitBounds.snapPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches two apps as an app pair.
|
||||
*/
|
||||
public void launchAppPair(WorkspaceItemInfo app1, WorkspaceItemInfo app2) {
|
||||
mSplitSelectStateController.getAppPairsController().launchAppPair(app1, app2);
|
||||
public void launchAppPair(AppPairIcon appPairIcon) {
|
||||
mSplitSelectStateController.getAppPairsController().launchAppPair(appPairIcon);
|
||||
}
|
||||
|
||||
public boolean canStartHomeSafely() {
|
||||
@@ -1272,6 +1300,16 @@ public class QuickstepLauncher extends Launcher {
|
||||
return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBubbleBarEnabled() {
|
||||
return (mTaskbarUIController != null && mTaskbarUIController.isBubbleBarEnabled());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasBubbles() {
|
||||
return (mTaskbarUIController != null && mTaskbarUIController.hasBubbles());
|
||||
}
|
||||
|
||||
private static final class LauncherTaskViewController extends
|
||||
TaskViewTouchController<Launcher> {
|
||||
|
||||
@@ -1321,18 +1359,12 @@ public class QuickstepLauncher extends Launcher {
|
||||
switch (name) {
|
||||
case "TextClock", "android.widget.TextClock" -> {
|
||||
TextClock tc = new TextClock(context, attrs);
|
||||
if (mAsyncClockEventDelegate == null) {
|
||||
mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
|
||||
}
|
||||
tc.setClockEventDelegate(mAsyncClockEventDelegate);
|
||||
tc.setClockEventDelegate(AsyncClockEventDelegate.INSTANCE.get(this));
|
||||
return tc;
|
||||
}
|
||||
case "AnalogClock", "android.widget.AnalogClock" -> {
|
||||
AnalogClock ac = new AnalogClock(context, attrs);
|
||||
if (mAsyncClockEventDelegate == null) {
|
||||
mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
|
||||
}
|
||||
ac.setClockEventDelegate(mAsyncClockEventDelegate);
|
||||
ac.setClockEventDelegate(AsyncClockEventDelegate.INSTANCE.get(this));
|
||||
return ac;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import com.android.launcher3.util.IntSet;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetHostView;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.widget.LauncherWidgetHolder;
|
||||
import com.android.launcher3.widget.custom.CustomWidgetManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -237,6 +238,14 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
|
||||
@Override
|
||||
public LauncherAppWidgetHostView createView(@NonNull Context context, int appWidgetId,
|
||||
@NonNull LauncherAppWidgetProviderInfo appWidget) {
|
||||
|
||||
if (appWidget.isCustomWidget()) {
|
||||
LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context);
|
||||
lahv.setAppWidget(appWidgetId, appWidget);
|
||||
CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
|
||||
return lahv;
|
||||
}
|
||||
|
||||
LauncherAppWidgetHostView widgetView = getPendingView(appWidgetId);
|
||||
if (widgetView != null) {
|
||||
removePendingView(appWidgetId);
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.android.launcher3.uioverrides.flags;
|
||||
|
||||
import static com.android.launcher3.uioverrides.flags.FlagsFactory.TEAMFOOD_FLAG;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.config.FeatureFlags.BooleanFlag;
|
||||
@@ -35,6 +37,21 @@ class DebugFlag extends BooleanFlag {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this flag's value has been modified from its default.
|
||||
* <p>
|
||||
* This helps to identify which flags have been toggled in log dumps and bug reports to
|
||||
* further help triaging and debugging.
|
||||
*/
|
||||
boolean currentValueModified() {
|
||||
switch (defaultValue) {
|
||||
case ENABLED: return !get();
|
||||
case TEAMFOOD: return TEAMFOOD_FLAG.get() != get();
|
||||
case DISABLED: return get();
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return key + ": defaultValue=" + defaultValue + ", mCurrentValue=" + get();
|
||||
|
||||
+155
-169
@@ -15,20 +15,30 @@
|
||||
*/
|
||||
package com.android.launcher3.uioverrides.flags;
|
||||
|
||||
import static android.content.Intent.ACTION_PACKAGE_ADDED;
|
||||
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
|
||||
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
|
||||
import static android.content.pm.PackageManager.GET_RESOLVED_FILTER;
|
||||
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
|
||||
import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
|
||||
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY;
|
||||
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT;
|
||||
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS;
|
||||
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT;
|
||||
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT;
|
||||
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE;
|
||||
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_TIMEOUT_MS;
|
||||
import static com.android.launcher3.LauncherPrefs.PRIVATE_SPACE_APPS;
|
||||
import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_HIGHLIGHT_KEY;
|
||||
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
|
||||
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
|
||||
import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
|
||||
import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_COUNT;
|
||||
import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_SEEN;
|
||||
import static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_DISCOVERY_TIP_COUNT;
|
||||
import static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN;
|
||||
import static com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -36,23 +46,17 @@ import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Pair;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceDataStore;
|
||||
@@ -63,17 +67,16 @@ import androidx.preference.PreferenceViewHolder;
|
||||
import androidx.preference.SeekBarPreference;
|
||||
import androidx.preference.SwitchPreference;
|
||||
|
||||
import com.android.launcher3.ConstantItem;
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher;
|
||||
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
|
||||
import com.android.launcher3.util.OnboardingPrefs;
|
||||
import com.android.launcher3.util.SimpleBroadcastReceiver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -81,39 +84,41 @@ import java.util.stream.Collectors;
|
||||
* Dev-build only UI allowing developers to toggle flag settings and plugins.
|
||||
* See {@link FeatureFlags}.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
|
||||
public class DeveloperOptionsUI {
|
||||
|
||||
private static final String ACTION_PLUGIN_SETTINGS = "com.android.systemui.action.PLUGIN_SETTINGS";
|
||||
private static final String ACTION_PLUGIN_SETTINGS =
|
||||
"com.android.systemui.action.PLUGIN_SETTINGS";
|
||||
private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
|
||||
|
||||
private final SimpleBroadcastReceiver mPluginReceiver =
|
||||
new SimpleBroadcastReceiver(i -> loadPluginPrefs());
|
||||
|
||||
private PreferenceScreen mPreferenceScreen;
|
||||
private final PreferenceFragmentCompat mFragment;
|
||||
private final PreferenceScreen mPreferenceScreen;
|
||||
|
||||
private PreferenceCategory mPluginsCategory;
|
||||
private FlagTogglerPrefUi mFlagTogglerPrefUi;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
mPluginReceiver.registerPkgActions(getContext(), null,
|
||||
ACTION_PACKAGE_ADDED, ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REMOVED);
|
||||
mPluginReceiver.register(getContext(), Intent.ACTION_USER_UNLOCKED);
|
||||
public DeveloperOptionsUI(PreferenceFragmentCompat fragment, PreferenceCategory flags) {
|
||||
mFragment = fragment;
|
||||
mPreferenceScreen = fragment.getPreferenceScreen();
|
||||
|
||||
mPreferenceScreen = getPreferenceManager().createPreferenceScreen(getContext());
|
||||
setPreferenceScreen(mPreferenceScreen);
|
||||
// Add search bar
|
||||
View listView = mFragment.getListView();
|
||||
ViewGroup parent = (ViewGroup) listView.getParent();
|
||||
View topBar = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.developer_options_top_bar, parent, false);
|
||||
parent.addView(topBar, parent.indexOfChild(listView));
|
||||
initSearch(topBar.findViewById(R.id.filter_box));
|
||||
|
||||
new FlagTogglerPrefUi(mFragment.requireActivity(), topBar.findViewById(R.id.flag_apply_btn))
|
||||
.applyTo(flags);
|
||||
|
||||
initFlags();
|
||||
loadPluginPrefs();
|
||||
maybeAddSandboxCategory();
|
||||
addOnboardingPrefsCatergory();
|
||||
if (FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()) {
|
||||
addAllAppsFromOverviewCatergory();
|
||||
}
|
||||
|
||||
if (getActivity() != null) {
|
||||
getActivity().setTitle("Developer Options");
|
||||
addCustomLpnhCategory();
|
||||
if (Flags.enablePrivateSpace()) {
|
||||
addCustomPrivateAppsCategory();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,21 +142,13 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
|
||||
pg.setVisible(hidden != count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
EditText filterBox = view.findViewById(R.id.filter_box);
|
||||
filterBox.setVisibility(VISIBLE);
|
||||
private void initSearch(EditText filterBox) {
|
||||
filterBox.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
|
||||
}
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
|
||||
}
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
@@ -160,85 +157,33 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
|
||||
}
|
||||
});
|
||||
|
||||
if (getArguments() != null) {
|
||||
String filter = getArguments().getString(EXTRA_FRAGMENT_ARG_KEY);
|
||||
if (mFragment.getArguments() != null) {
|
||||
String filter = mFragment.getArguments().getString(EXTRA_FRAGMENT_HIGHLIGHT_KEY);
|
||||
// Normally EXTRA_FRAGMENT_ARG_KEY is used to highlight the preference with the given
|
||||
// key. This is a slight variation where we instead filter by the human-readable titles.
|
||||
if (filter != null) {
|
||||
filterBox.setText(filter);
|
||||
}
|
||||
}
|
||||
|
||||
View listView = getListView();
|
||||
final int bottomPadding = listView.getPaddingBottom();
|
||||
listView.setOnApplyWindowInsetsListener((v, insets) -> {
|
||||
v.setPadding(
|
||||
v.getPaddingLeft(),
|
||||
v.getPaddingTop(),
|
||||
v.getPaddingRight(),
|
||||
bottomPadding + insets.getSystemWindowInsetBottom());
|
||||
return insets.consumeSystemWindowInsets();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
mPluginReceiver.unregisterReceiverSafely(getContext());
|
||||
}
|
||||
|
||||
private PreferenceCategory newCategory(String title) {
|
||||
return newCategory(title, null);
|
||||
}
|
||||
|
||||
private PreferenceCategory newCategory(String title, @Nullable String summary) {
|
||||
PreferenceCategory category = new PreferenceCategory(getContext());
|
||||
category.setOrder(Preference.DEFAULT_ORDER);
|
||||
category.setTitle(title);
|
||||
if (!TextUtils.isEmpty(summary)) {
|
||||
category.setSummary(summary);
|
||||
}
|
||||
mPreferenceScreen.addPreference(category);
|
||||
return category;
|
||||
}
|
||||
|
||||
private void initFlags() {
|
||||
if (!FeatureFlags.showFlagTogglerUi(getContext())) {
|
||||
return;
|
||||
}
|
||||
|
||||
mFlagTogglerPrefUi = new FlagTogglerPrefUi(this);
|
||||
mFlagTogglerPrefUi.applyTo(newCategory("Feature flags", "Long press to reset"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
if (mFlagTogglerPrefUi != null) {
|
||||
mFlagTogglerPrefUi.onCreateOptionsMenu(menu);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (mFlagTogglerPrefUi != null) {
|
||||
mFlagTogglerPrefUi.onOptionsItemSelected(item);
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
if (mFlagTogglerPrefUi != null) {
|
||||
mFlagTogglerPrefUi.onStop();
|
||||
}
|
||||
super.onStop();
|
||||
private Context getContext() {
|
||||
return mFragment.requireContext();
|
||||
}
|
||||
|
||||
private void loadPluginPrefs() {
|
||||
if (mPluginsCategory != null) {
|
||||
mPreferenceScreen.removePreference(mPluginsCategory);
|
||||
}
|
||||
if (!PluginManagerWrapper.hasPlugins(getActivity())) {
|
||||
if (!PluginManagerWrapper.hasPlugins(getContext())) {
|
||||
mPluginsCategory = null;
|
||||
return;
|
||||
}
|
||||
@@ -311,118 +256,159 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
|
||||
launchTutorialStepMenuPreference.setKey("launchTutorialStepMenu");
|
||||
launchTutorialStepMenuPreference.setTitle("Launch Gesture Tutorial Steps menu");
|
||||
launchTutorialStepMenuPreference.setSummary("Select a gesture tutorial step.");
|
||||
launchTutorialStepMenuPreference.setOnPreferenceClickListener(preference -> {
|
||||
startActivity(launchSandboxIntent.putExtra("use_tutorial_menu", true));
|
||||
return true;
|
||||
});
|
||||
launchTutorialStepMenuPreference.setIntent(
|
||||
new Intent(launchSandboxIntent).putExtra("use_tutorial_menu", true));
|
||||
|
||||
sandboxCategory.addPreference(launchTutorialStepMenuPreference);
|
||||
Preference launchOnboardingTutorialPreference = new Preference(context);
|
||||
launchOnboardingTutorialPreference.setKey("launchOnboardingTutorial");
|
||||
launchOnboardingTutorialPreference.setTitle("Launch Onboarding Tutorial");
|
||||
launchOnboardingTutorialPreference.setSummary("Learn the basic navigation gestures.");
|
||||
launchOnboardingTutorialPreference.setOnPreferenceClickListener(preference -> {
|
||||
startActivity(launchSandboxIntent
|
||||
.putExtra("use_tutorial_menu", false)
|
||||
.putExtra("tutorial_steps",
|
||||
new String[] {
|
||||
"HOME_NAVIGATION",
|
||||
"BACK_NAVIGATION",
|
||||
"OVERVIEW_NAVIGATION"}));
|
||||
return true;
|
||||
});
|
||||
launchTutorialStepMenuPreference.setIntent(new Intent(launchSandboxIntent)
|
||||
.putExtra("use_tutorial_menu", false)
|
||||
.putExtra("tutorial_steps",
|
||||
new String[] {
|
||||
"HOME_NAVIGATION",
|
||||
"BACK_NAVIGATION",
|
||||
"OVERVIEW_NAVIGATION"}));
|
||||
|
||||
sandboxCategory.addPreference(launchOnboardingTutorialPreference);
|
||||
Preference launchBackTutorialPreference = new Preference(context);
|
||||
launchBackTutorialPreference.setKey("launchBackTutorial");
|
||||
launchBackTutorialPreference.setTitle("Launch Back Tutorial");
|
||||
launchBackTutorialPreference.setSummary("Learn how to use the Back gesture");
|
||||
launchBackTutorialPreference.setOnPreferenceClickListener(preference -> {
|
||||
startActivity(launchSandboxIntent
|
||||
launchBackTutorialPreference.setIntent(new Intent(launchSandboxIntent)
|
||||
.putExtra("use_tutorial_menu", false)
|
||||
.putExtra("tutorial_steps", new String[] {"BACK_NAVIGATION"}));
|
||||
return true;
|
||||
});
|
||||
|
||||
sandboxCategory.addPreference(launchBackTutorialPreference);
|
||||
Preference launchHomeTutorialPreference = new Preference(context);
|
||||
launchHomeTutorialPreference.setKey("launchHomeTutorial");
|
||||
launchHomeTutorialPreference.setTitle("Launch Home Tutorial");
|
||||
launchHomeTutorialPreference.setSummary("Learn how to use the Home gesture");
|
||||
launchHomeTutorialPreference.setOnPreferenceClickListener(preference -> {
|
||||
startActivity(launchSandboxIntent
|
||||
launchHomeTutorialPreference.setIntent(new Intent(launchSandboxIntent)
|
||||
.putExtra("use_tutorial_menu", false)
|
||||
.putExtra("tutorial_steps", new String[] {"HOME_NAVIGATION"}));
|
||||
return true;
|
||||
});
|
||||
|
||||
sandboxCategory.addPreference(launchHomeTutorialPreference);
|
||||
Preference launchOverviewTutorialPreference = new Preference(context);
|
||||
launchOverviewTutorialPreference.setKey("launchOverviewTutorial");
|
||||
launchOverviewTutorialPreference.setTitle("Launch Overview Tutorial");
|
||||
launchOverviewTutorialPreference.setSummary("Learn how to use the Overview gesture");
|
||||
launchOverviewTutorialPreference.setOnPreferenceClickListener(preference -> {
|
||||
startActivity(launchSandboxIntent
|
||||
launchOverviewTutorialPreference.setIntent(new Intent(launchSandboxIntent)
|
||||
.putExtra("use_tutorial_menu", false)
|
||||
.putExtra("tutorial_steps", new String[] {"OVERVIEW_NAVIGATION"}));
|
||||
return true;
|
||||
});
|
||||
|
||||
sandboxCategory.addPreference(launchOverviewTutorialPreference);
|
||||
Preference launchSecondaryDisplayPreference = new Preference(context);
|
||||
launchSecondaryDisplayPreference.setKey("launchSecondaryDisplay");
|
||||
launchSecondaryDisplayPreference.setTitle("Launch Secondary Display");
|
||||
launchSecondaryDisplayPreference.setSummary("Launch secondary display activity");
|
||||
launchSecondaryDisplayPreference.setOnPreferenceClickListener(preference -> {
|
||||
startActivity(new Intent(context, SecondaryDisplayLauncher.class));
|
||||
return true;
|
||||
});
|
||||
sandboxCategory.addPreference(launchSecondaryDisplayPreference);
|
||||
launchSecondaryDisplayPreference.setIntent(
|
||||
new Intent(context, SecondaryDisplayLauncher.class));
|
||||
|
||||
}
|
||||
|
||||
private void addOnboardingPrefsCatergory() {
|
||||
PreferenceCategory onboardingCategory = newCategory("Onboarding Flows");
|
||||
onboardingCategory.setSummary("Reset these if you want to see the education again.");
|
||||
for (Map.Entry<String, String[]> titleAndKeys : OnboardingPrefs.ALL_PREF_KEYS.entrySet()) {
|
||||
String title = titleAndKeys.getKey();
|
||||
String[] keys = titleAndKeys.getValue();
|
||||
Preference onboardingPref = new Preference(getContext());
|
||||
onboardingPref.setTitle(title);
|
||||
onboardingPref.setSummary("Tap to reset");
|
||||
onboardingPref.setOnPreferenceClickListener(preference -> {
|
||||
SharedPreferences.Editor sharedPrefsEdit = LauncherPrefs.getPrefs(getContext())
|
||||
.edit();
|
||||
for (String key : keys) {
|
||||
sharedPrefsEdit.remove(key);
|
||||
}
|
||||
sharedPrefsEdit.apply();
|
||||
Toast.makeText(getContext(), "Reset " + title, Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
});
|
||||
onboardingCategory.addPreference(onboardingPref);
|
||||
}
|
||||
|
||||
onboardingCategory.addPreference(createOnboardPref("All Apps Bounce",
|
||||
HOME_BOUNCE_SEEN.getSharedPrefKey(), HOME_BOUNCE_COUNT.getSharedPrefKey()));
|
||||
onboardingCategory.addPreference(createOnboardPref("Hybrid Hotseat Education",
|
||||
HOTSEAT_DISCOVERY_TIP_COUNT.getSharedPrefKey(),
|
||||
HOTSEAT_LONGPRESS_TIP_SEEN.getSharedPrefKey()));
|
||||
onboardingCategory.addPreference(createOnboardPref("Taskbar Education",
|
||||
TASKBAR_EDU_TOOLTIP_STEP.getSharedPrefKey()));
|
||||
onboardingCategory.addPreference(createOnboardPref("All Apps Visited Count",
|
||||
ALL_APPS_VISITED_COUNT.getSharedPrefKey()));
|
||||
}
|
||||
|
||||
private Preference createOnboardPref(String title, String... keys) {
|
||||
Preference onboardingPref = new Preference(getContext());
|
||||
onboardingPref.setTitle(title);
|
||||
onboardingPref.setSummary("Tap to reset");
|
||||
onboardingPref.setOnPreferenceClickListener(preference -> {
|
||||
SharedPreferences.Editor sharedPrefsEdit = LauncherPrefs.getPrefs(getContext())
|
||||
.edit();
|
||||
for (String key : keys) {
|
||||
sharedPrefsEdit.remove(key);
|
||||
}
|
||||
sharedPrefsEdit.apply();
|
||||
Toast.makeText(getContext(), "Reset " + title, Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
});
|
||||
return onboardingPref;
|
||||
}
|
||||
|
||||
private void addAllAppsFromOverviewCatergory() {
|
||||
PreferenceCategory category = newCategory("All Apps from Overview Config");
|
||||
category.addPreference(createSeekBarPreference("Threshold to open All Apps from Overview",
|
||||
105, 500, 100, ALL_APPS_OVERVIEW_THRESHOLD));
|
||||
}
|
||||
|
||||
SeekBarPreference thresholdPref = new SeekBarPreference(getContext());
|
||||
thresholdPref.setTitle("Threshold to open All Apps from Overview");
|
||||
thresholdPref.setSingleLineTitle(false);
|
||||
private void addCustomLpnhCategory() {
|
||||
PreferenceCategory category = newCategory("Long Press Nav Handle Config");
|
||||
if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
|
||||
category.addPreference(createSeekBarPreference("Slop multiplier (applied to edge slop, "
|
||||
+ "which is generally already 50% higher than touch slop)",
|
||||
25, 200, 100, LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE));
|
||||
category.addPreference(createSeekBarPreference("Trigger milliseconds",
|
||||
100, 500, 1, LONG_PRESS_NAV_HANDLE_TIMEOUT_MS));
|
||||
}
|
||||
if (FeatureFlags.ENABLE_SEARCH_HAPTIC_HINT.get()) {
|
||||
category.addPreference(createSeekBarPreference("Haptic hint start scale",
|
||||
0, 100, 100, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT));
|
||||
category.addPreference(createSeekBarPreference("Haptic hint end scale",
|
||||
0, 100, 100, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT));
|
||||
category.addPreference(createSeekBarPreference("Haptic hint scale exponent",
|
||||
1, 5, 1, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT));
|
||||
category.addPreference(createSeekBarPreference("Haptic hint iterations (12 ms each)",
|
||||
0, 200, 1, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS));
|
||||
category.addPreference(createSeekBarPreference("Haptic hint delay (ms)",
|
||||
0, 400, 1, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY));
|
||||
}
|
||||
}
|
||||
|
||||
// These values are 100x swipe up shift value (100 = where overview sits).
|
||||
thresholdPref.setMax(500);
|
||||
thresholdPref.setMin(105);
|
||||
thresholdPref.setUpdatesContinuously(true);
|
||||
thresholdPref.setIconSpaceReserved(false);
|
||||
private void addCustomPrivateAppsCategory() {
|
||||
PreferenceCategory category = newCategory("Apps in Private Space Config");
|
||||
category.addPreference(createSeekBarPreference(
|
||||
"Number of Apps to put in private region", 0, 100, 1, PRIVATE_SPACE_APPS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a preference with text and a seek bar. Should be added to a PreferenceCategory.
|
||||
*
|
||||
* @param title text to show for this seek bar
|
||||
* @param min min value for the seek bar
|
||||
* @param max max value for the seek bar
|
||||
* @param scale how much to divide the value to convert int to float
|
||||
* @param launcherPref used to store the current value
|
||||
*/
|
||||
private SeekBarPreference createSeekBarPreference(String title, int min, int max, int scale,
|
||||
ConstantItem<Integer> launcherPref) {
|
||||
SeekBarPreference seekBarPref = new SeekBarPreference(getContext());
|
||||
seekBarPref.setTitle(title);
|
||||
seekBarPref.setSingleLineTitle(false);
|
||||
|
||||
seekBarPref.setMax(max);
|
||||
seekBarPref.setMin(min);
|
||||
seekBarPref.setUpdatesContinuously(true);
|
||||
seekBarPref.setIconSpaceReserved(false);
|
||||
// Don't directly save to shared prefs, use LauncherPrefs instead.
|
||||
thresholdPref.setPersistent(false);
|
||||
thresholdPref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
LauncherPrefs.get(getContext()).put(ALL_APPS_OVERVIEW_THRESHOLD, newValue);
|
||||
preference.setSummary(String.valueOf((int) newValue / 100f));
|
||||
seekBarPref.setPersistent(false);
|
||||
seekBarPref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
LauncherPrefs.get(getContext()).put(launcherPref, newValue);
|
||||
preference.setSummary(String.valueOf(scale == 1 ? newValue
|
||||
: (int) newValue / (float) scale));
|
||||
return true;
|
||||
});
|
||||
int value = LauncherPrefs.get(getContext()).get(ALL_APPS_OVERVIEW_THRESHOLD);
|
||||
thresholdPref.setValue(value);
|
||||
int value = LauncherPrefs.get(getContext()).get(launcherPref);
|
||||
seekBarPref.setValue(value);
|
||||
// For some reason the initial value is not triggering the summary update, so call manually.
|
||||
thresholdPref.getOnPreferenceChangeListener().onPreferenceChange(thresholdPref, value);
|
||||
|
||||
category.addPreference(thresholdPref);
|
||||
seekBarPref.setSummary(String.valueOf(scale == 1 ? value
|
||||
: value / (float) scale));
|
||||
return seekBarPref;
|
||||
}
|
||||
|
||||
private String toName(String action) {
|
||||
@@ -28,6 +28,11 @@ class DeviceFlag extends DebugFlag {
|
||||
mDefaultValueInCode = defaultValueInCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean currentValueModified() {
|
||||
return super.currentValueModified() || mDefaultValueInCode != get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ", mDefaultValueInCode=" + mDefaultValueInCode;
|
||||
|
||||
@@ -19,24 +19,23 @@ package com.android.launcher3.uioverrides.flags;
|
||||
import static com.android.launcher3.config.FeatureFlags.FlagState.TEAMFOOD;
|
||||
import static com.android.launcher3.uioverrides.flags.FlagsFactory.TEAMFOOD_FLAG;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Process;
|
||||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.preference.PreferenceDataStore;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceGroup;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
import androidx.preference.SwitchPreference;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -44,12 +43,13 @@ import java.util.Set;
|
||||
/**
|
||||
* Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}.
|
||||
*/
|
||||
public final class FlagTogglerPrefUi {
|
||||
public final class FlagTogglerPrefUi implements ActivityLifecycleCallbacksAdapter {
|
||||
|
||||
private static final String TAG = "FlagTogglerPrefFrag";
|
||||
|
||||
private final PreferenceFragmentCompat mFragment;
|
||||
private final View mFlagsApplyButton;
|
||||
private final Context mContext;
|
||||
|
||||
private final PreferenceDataStore mDataStore = new PreferenceDataStore() {
|
||||
|
||||
@Override
|
||||
@@ -64,9 +64,17 @@ public final class FlagTogglerPrefUi {
|
||||
}
|
||||
};
|
||||
|
||||
public FlagTogglerPrefUi(PreferenceFragmentCompat fragment) {
|
||||
mFragment = fragment;
|
||||
mContext = fragment.getActivity();
|
||||
public FlagTogglerPrefUi(Activity activity, View flagsApplyButton) {
|
||||
mFlagsApplyButton = flagsApplyButton;
|
||||
mContext = mFlagsApplyButton.getContext();
|
||||
activity.registerActivityLifecycleCallbacks(this);
|
||||
|
||||
mFlagsApplyButton.setOnClickListener(v -> {
|
||||
FlagsFactory.getSharedPreferences().edit().commit();
|
||||
Log.e(TAG,
|
||||
"Killing launcher process " + Process.myPid() + " to apply new flag values");
|
||||
System.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
public void applyTo(PreferenceGroup parent) {
|
||||
@@ -137,27 +145,11 @@ public final class FlagTogglerPrefUi {
|
||||
}
|
||||
|
||||
private void updateMenu() {
|
||||
mFragment.setHasOptionsMenu(anyChanged());
|
||||
mFragment.getActivity().invalidateOptionsMenu();
|
||||
mFlagsApplyButton.setVisibility(anyChanged() ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
|
||||
public void onCreateOptionsMenu(Menu menu) {
|
||||
if (anyChanged()) {
|
||||
menu.add(0, R.id.menu_apply_flags, 0, "Apply")
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
}
|
||||
}
|
||||
|
||||
public void onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.menu_apply_flags) {
|
||||
FlagsFactory.getSharedPreferences().edit().commit();
|
||||
Log.e(TAG,
|
||||
"Killing launcher process " + Process.myPid() + " to apply new flag values");
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void onStop() {
|
||||
@Override
|
||||
public void onActivityStopped(Activity activity) {
|
||||
if (anyChanged()) {
|
||||
Toast.makeText(mContext, "Flag won't be applied until you restart launcher",
|
||||
Toast.LENGTH_LONG).show();
|
||||
|
||||
@@ -57,6 +57,7 @@ public class FlagsFactory {
|
||||
public static final String NAMESPACE_LAUNCHER = "launcher";
|
||||
|
||||
private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
|
||||
private static final List<IntFlag> sIntFlags = new ArrayList<>();
|
||||
private static SharedPreferences sSharedPreferences;
|
||||
|
||||
static final BooleanFlag TEAMFOOD_FLAG = getReleaseFlag(
|
||||
@@ -132,7 +133,14 @@ public class FlagsFactory {
|
||||
public static IntFlag getIntFlag(
|
||||
int bugId, String key, int defaultValueInCode, String description) {
|
||||
INSTANCE.mKeySet.add(key);
|
||||
return new IntFlag(DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, defaultValueInCode));
|
||||
int defaultValue = DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, defaultValueInCode);
|
||||
if (IS_DEBUG_DEVICE) {
|
||||
IntDeviceFlag flag = new IntDeviceFlag(key, defaultValue, defaultValueInCode);
|
||||
sIntFlags.add(flag);
|
||||
return flag;
|
||||
} else {
|
||||
return new IntFlag(defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
static List<DebugFlag> getDebugFlags() {
|
||||
@@ -163,18 +171,25 @@ public class FlagsFactory {
|
||||
return;
|
||||
}
|
||||
pw.println("DeviceFlags:");
|
||||
pw.println(" BooleanFlags:");
|
||||
synchronized (sDebugFlags) {
|
||||
for (DebugFlag flag : sDebugFlags) {
|
||||
if (flag instanceof DeviceFlag) {
|
||||
pw.println(" " + flag);
|
||||
pw.println((flag.currentValueModified() ? " ->" : " ") + flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
pw.println("DebugFlags:");
|
||||
pw.println(" IntFlags:");
|
||||
synchronized (sIntFlags) {
|
||||
for (IntFlag flag : sIntFlags) {
|
||||
pw.println(" " + flag);
|
||||
}
|
||||
}
|
||||
pw.println(" DebugFlags:");
|
||||
synchronized (sDebugFlags) {
|
||||
for (DebugFlag flag : sDebugFlags) {
|
||||
if (!(flag instanceof DeviceFlag)) {
|
||||
pw.println(" " + flag);
|
||||
pw.println((flag.currentValueModified() ? " ->" : " ") + flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.launcher3.uioverrides.flags;
|
||||
|
||||
import com.android.launcher3.config.FeatureFlags.IntFlag;
|
||||
|
||||
public class IntDeviceFlag extends IntFlag {
|
||||
public final String key;
|
||||
private final int mDefaultValueInCode;
|
||||
|
||||
public IntDeviceFlag(String key, int currentValue, int defaultValueInCode) {
|
||||
super(currentValue);
|
||||
this.key = key;
|
||||
mDefaultValueInCode = defaultValueInCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return key + ": mCurrentValue=" + get() + ", defaultValueInCode=" + mDefaultValueInCode;
|
||||
}
|
||||
}
|
||||
@@ -121,13 +121,13 @@ public class AllAppsState extends LauncherState {
|
||||
@Override
|
||||
public int getFloatingSearchBarRestingMarginStart(Launcher launcher) {
|
||||
DeviceProfile dp = launcher.getDeviceProfile();
|
||||
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin();
|
||||
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin(launcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFloatingSearchBarRestingMarginEnd(Launcher launcher) {
|
||||
DeviceProfile dp = launcher.getDeviceProfile();
|
||||
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin();
|
||||
return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin(launcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,6 +17,7 @@ package com.android.launcher3.uioverrides.states;
|
||||
|
||||
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
|
||||
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
|
||||
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
@@ -26,7 +27,6 @@ import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.allapps.AllAppsTransitionController;
|
||||
import com.android.quickstep.util.LayoutUtils;
|
||||
import com.android.quickstep.views.DesktopTaskView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
|
||||
/**
|
||||
@@ -90,7 +90,7 @@ public class BackgroundAppState extends OverviewState {
|
||||
|
||||
@Override
|
||||
protected float getDepthUnchecked(Context context) {
|
||||
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
|
||||
if (isDesktopModeSupported()) {
|
||||
if (Launcher.getLauncher(context).areFreeformTasksVisible()) {
|
||||
// Don't blur the background while freeform tasks are visible
|
||||
return 0;
|
||||
|
||||
@@ -21,9 +21,9 @@ import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
|
||||
import com.android.launcher3.BaseDraggingActivity;
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
|
||||
/**
|
||||
@@ -72,7 +72,7 @@ public class OverviewModalTaskState extends OverviewState {
|
||||
|
||||
@Override
|
||||
public boolean isTaskbarStashed(Launcher launcher) {
|
||||
if (FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) {
|
||||
if (Flags.enableGridOnlyOverview()) {
|
||||
return true;
|
||||
}
|
||||
return super.isTaskbarStashed(launcher);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.android.launcher3.uioverrides.states;
|
||||
|
||||
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
|
||||
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
@@ -23,7 +24,6 @@ import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.util.Themes;
|
||||
import com.android.quickstep.views.DesktopTaskView;
|
||||
|
||||
/**
|
||||
* State to indicate we are about to launch a recent task. Note that this state is only used when
|
||||
@@ -46,7 +46,7 @@ public class QuickSwitchState extends BackgroundAppState {
|
||||
|
||||
@Override
|
||||
public int getWorkspaceScrimColor(Launcher launcher) {
|
||||
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
|
||||
if (isDesktopModeSupported()) {
|
||||
if (launcher.areFreeformTasksVisible()) {
|
||||
// No scrim while freeform tasks are visible
|
||||
return Color.TRANSPARENT;
|
||||
|
||||
+16
-2
@@ -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);
|
||||
|
||||
+2
-3
@@ -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;
|
||||
}
|
||||
|
||||
+3
-4
@@ -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;
|
||||
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
+9
@@ -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;
|
||||
|
||||
+3
-2
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package com.android.launcher3.uioverrides.touchcontrollers;
|
||||
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_TOUCH_CONTROLLER_NO_INTERCEPT;
|
||||
import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
|
||||
import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH;
|
||||
|
||||
@@ -112,7 +112,8 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
|
||||
// If we are already animating from a previous state, we can intercept.
|
||||
return true;
|
||||
}
|
||||
if (AbstractFloatingView.getTopOpenViewWithType(mActivity, TYPE_ACCESSIBLE) != null) {
|
||||
if (AbstractFloatingView.getTopOpenViewWithType(
|
||||
mActivity, TYPE_TOUCH_CONTROLLER_NO_INTERCEPT) != null) {
|
||||
return false;
|
||||
}
|
||||
return isRecentsInteractive();
|
||||
|
||||
@@ -24,6 +24,8 @@ import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
|
||||
import static com.android.app.animation.Interpolators.DECELERATE;
|
||||
import static com.android.app.animation.Interpolators.OVERSHOOT_1_2;
|
||||
import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
|
||||
import static com.android.launcher3.BaseActivity.EVENT_STARTED;
|
||||
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
|
||||
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
|
||||
import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
|
||||
@@ -51,8 +53,10 @@ import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHE
|
||||
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
|
||||
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION;
|
||||
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED;
|
||||
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.INVALID_VELOCITY_ON_SWIPE_UP;
|
||||
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.LAUNCHER_DESTROYED;
|
||||
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET;
|
||||
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
|
||||
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
|
||||
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
|
||||
|
||||
@@ -61,7 +65,6 @@ import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.TaskInfo;
|
||||
import android.app.WindowConfiguration;
|
||||
@@ -78,6 +81,7 @@ import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.RemoteAnimationTarget;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.View;
|
||||
import android.view.View.OnApplyWindowInsetsListener;
|
||||
import android.view.ViewGroup;
|
||||
@@ -88,6 +92,7 @@ import android.view.animation.Interpolator;
|
||||
import android.widget.Toast;
|
||||
import android.window.PictureInPictureSurfaceTransaction;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
@@ -108,7 +113,6 @@ import com.android.launcher3.statemanager.StatefulActivity;
|
||||
import com.android.launcher3.taskbar.TaskbarThresholdUtils;
|
||||
import com.android.launcher3.taskbar.TaskbarUIController;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.util.TraceHelper;
|
||||
@@ -131,7 +135,6 @@ import com.android.quickstep.util.SurfaceTransaction;
|
||||
import com.android.quickstep.util.SurfaceTransactionApplier;
|
||||
import com.android.quickstep.util.SwipePipToHomeAnimator;
|
||||
import com.android.quickstep.util.TaskViewSimulator;
|
||||
import com.android.quickstep.views.DesktopTaskView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
|
||||
@@ -177,25 +180,19 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
protected @Nullable RecentsAnimationController mRecentsAnimationController;
|
||||
protected @Nullable RecentsAnimationController mDeferredCleanupRecentsAnimationController;
|
||||
protected RecentsAnimationTargets mRecentsAnimationTargets;
|
||||
protected T mActivity;
|
||||
protected @Nullable T mActivity;
|
||||
protected @Nullable Q mRecentsView;
|
||||
protected Runnable mGestureEndCallback;
|
||||
protected MultiStateCallback mStateCallback;
|
||||
protected boolean mCanceled;
|
||||
private boolean mRecentsViewScrollLinked = false;
|
||||
private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks =
|
||||
new ActivityLifecycleCallbacksAdapter() {
|
||||
@Override
|
||||
public void onActivityDestroyed(Activity activity) {
|
||||
if (mActivity != activity) {
|
||||
return;
|
||||
}
|
||||
ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED);
|
||||
mRecentsView = null;
|
||||
mActivity = null;
|
||||
mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
|
||||
}
|
||||
};
|
||||
|
||||
private final Runnable mLauncherOnDestroyCallback = () -> {
|
||||
ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED);
|
||||
mRecentsView = null;
|
||||
mActivity = null;
|
||||
mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
|
||||
};
|
||||
|
||||
private static int FLAG_COUNT = 0;
|
||||
private static int getNextStateFlag(String name) {
|
||||
@@ -316,8 +313,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
private final int mSplashMainWindowShiftLength;
|
||||
|
||||
private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
|
||||
private final Runnable mLauncherOnStartCallback = this::onLauncherStart;
|
||||
|
||||
private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
|
||||
@Nullable private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
|
||||
protected boolean mIsSwipingPipToHome;
|
||||
// TODO(b/195473090) no split PIP for now, remove once we have more clarity
|
||||
// can try to have RectFSpringAnim evaluate multiple rects at once
|
||||
@@ -355,7 +353,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
return ROTATION_0;
|
||||
}
|
||||
return mRecentsView.getPagedViewOrientedState().getRecentsActivityRotation();
|
||||
}, inputConsumer, /* callback = */ () -> {
|
||||
}, inputConsumer, /* onTouchDownCallback = */ () -> {
|
||||
endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
|
||||
endLauncherTransitionController();
|
||||
}, new InputProxyHandlerFactory(mActivityInterface, mGestureState));
|
||||
@@ -489,6 +487,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
|
||||
return true;
|
||||
}
|
||||
resetLauncherListeners();
|
||||
|
||||
// The launcher may have been recreated as a result of device rotation.
|
||||
int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES;
|
||||
@@ -512,7 +511,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
if (alreadyOnHome) {
|
||||
onLauncherStart();
|
||||
} else {
|
||||
activity.runOnceOnStart(this::onLauncherStart);
|
||||
activity.addEventCallback(EVENT_STARTED, mLauncherOnStartCallback);
|
||||
}
|
||||
|
||||
// Set up a entire animation lifecycle callback to notify the current recents view when
|
||||
@@ -537,9 +536,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
|
||||
setupRecentsViewUi();
|
||||
mRecentsView.runOnPageScrollsInitialized(this::linkRecentsViewScroll);
|
||||
activity.runOnBindToTouchInteractionService(this::onLauncherBindToService);
|
||||
|
||||
mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
|
||||
mActivity.runOnBindToTouchInteractionService(this::onLauncherBindToService);
|
||||
mActivity.addEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -552,7 +550,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
|
||||
private void onLauncherStart() {
|
||||
final T activity = mActivityInterface.getCreatedActivity();
|
||||
if (mActivity != activity) {
|
||||
if (activity == null || mActivity != activity) {
|
||||
return;
|
||||
}
|
||||
if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
|
||||
@@ -676,6 +674,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
if (Arrays.stream(runningTasks).anyMatch(Objects::isNull)) {
|
||||
return;
|
||||
}
|
||||
if (mRecentsView == null) {
|
||||
return;
|
||||
}
|
||||
mRecentsView.onGestureAnimationStart(runningTasks, mDeviceState.getRotationTouchHelper());
|
||||
}
|
||||
|
||||
@@ -854,7 +855,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
if (windowInsets.isVisible(WindowInsets.Type.ime())) {
|
||||
return result;
|
||||
}
|
||||
buildAnimationController();
|
||||
if (mGestureState.getEndTarget() == null) {
|
||||
buildAnimationController();
|
||||
}
|
||||
// Reapply the current shift to ensure it takes new insets into account, e.g. when long
|
||||
// pressing to stash taskbar without moving the finger.
|
||||
onCurrentShiftUpdated();
|
||||
@@ -922,6 +925,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
// needs to be canceled
|
||||
mRecentsAnimationController.setWillFinishToHome(swipeUpThresholdPassed);
|
||||
|
||||
if (mActivity == null) return;
|
||||
if (swipeUpThresholdPassed) {
|
||||
mActivity.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0);
|
||||
} else {
|
||||
@@ -935,7 +939,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
public void onRecentsAnimationStart(RecentsAnimationController controller,
|
||||
RecentsAnimationTargets targets) {
|
||||
super.onRecentsAnimationStart(controller, targets);
|
||||
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED && targets.hasDesktopTasks()) {
|
||||
if (isDesktopModeSupported() && targets.hasDesktopTasks()) {
|
||||
mRemoteTargetHandles = mTargetGluer.assignTargetsForDesktop(targets);
|
||||
} else {
|
||||
int untrimmedAppCount = mRemoteTargetHandles.length;
|
||||
@@ -1161,7 +1165,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
|
||||
// Notify the SysUI to use fade-in animation when entering PiP
|
||||
SystemUiProxy.INSTANCE.get(mContext).setPipAnimationTypeToAlpha();
|
||||
if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
|
||||
if (isDesktopModeSupported()) {
|
||||
// Notify the SysUI to stash desktop apps if they are visible
|
||||
DesktopVisibilityController desktopVisibilityController =
|
||||
mActivityInterface.getDesktopVisibilityController();
|
||||
@@ -1189,7 +1193,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
break;
|
||||
}
|
||||
ActiveGestureLog.INSTANCE.addLog(
|
||||
/* event= */ "onSettledOnEndTarget " + endTarget,
|
||||
new ActiveGestureLog.CompoundString("onSettledOnEndTarget ")
|
||||
.append(endTarget.name()),
|
||||
/* gestureEvent= */ ON_SETTLED_ON_END_TARGET);
|
||||
}
|
||||
|
||||
@@ -1213,14 +1218,19 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
|
||||
private GestureEndTarget calculateEndTarget(
|
||||
PointF velocityPxPerMs, float endVelocityPxPerMs, boolean isFlingY, boolean isCancel) {
|
||||
|
||||
ActiveGestureErrorDetector.GestureEvent gestureEvent =
|
||||
velocityPxPerMs.x == 0 && velocityPxPerMs.y == 0
|
||||
? INVALID_VELOCITY_ON_SWIPE_UP
|
||||
: null;
|
||||
ActiveGestureLog.INSTANCE.addLog(
|
||||
new ActiveGestureLog.CompoundString("calculateEndTarget: velocities=(x=")
|
||||
.append(Float.toString(dpiFromPx(velocityPxPerMs.x)))
|
||||
.append(dpiFromPx(velocityPxPerMs.x))
|
||||
.append("dp/ms, y=")
|
||||
.append(Float.toString(dpiFromPx(velocityPxPerMs.y)))
|
||||
.append(dpiFromPx(velocityPxPerMs.y))
|
||||
.append("dp/ms), angle=")
|
||||
.append(Double.toString(Math.toDegrees(Math.atan2(
|
||||
-velocityPxPerMs.y, velocityPxPerMs.x)))));
|
||||
.append(Math.toDegrees(Math.atan2(
|
||||
-velocityPxPerMs.y, velocityPxPerMs.x))), gestureEvent);
|
||||
|
||||
if (mGestureState.isHandlingAtomicEvent()) {
|
||||
// Button mode, this is only used to go to recents.
|
||||
@@ -1240,7 +1250,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
return LAST_TASK;
|
||||
}
|
||||
|
||||
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED && endTarget == NEW_TASK) {
|
||||
if (isDesktopModeSupported() && endTarget == NEW_TASK) {
|
||||
// TODO(b/268075592): add support for quickswitch to/from desktop
|
||||
return LAST_TASK;
|
||||
}
|
||||
@@ -1257,8 +1267,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
return isSwipeUp ? ALL_APPS : LAST_TASK;
|
||||
}
|
||||
if (!isSwipeUp) {
|
||||
final boolean isCenteredOnNewTask =
|
||||
mRecentsView.getDestinationPage() != mRecentsView.getRunningTaskIndex();
|
||||
final boolean isCenteredOnNewTask = mRecentsView != null
|
||||
&& mRecentsView.getDestinationPage() != mRecentsView.getRunningTaskIndex();
|
||||
return willGoToNewTask || isCenteredOnNewTask ? NEW_TASK : LAST_TASK;
|
||||
}
|
||||
|
||||
@@ -1491,7 +1501,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
if (mGestureState.getEndTarget().isLauncher) {
|
||||
// This is also called when the launcher is resumed, in order to clear the pending
|
||||
// widgets that have yet to be configured.
|
||||
DragView.removeAllViews(mActivity);
|
||||
if (mActivity != null) {
|
||||
DragView.removeAllViews(mActivity);
|
||||
}
|
||||
|
||||
TaskStackChangeListeners.getInstance().registerTaskStackListener(
|
||||
mActivityRestartListener);
|
||||
@@ -1534,11 +1546,13 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
HomeAnimationFactory homeAnimFactory =
|
||||
createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
|
||||
runningTaskTarget);
|
||||
mIsSwipingPipToHome = !mIsSwipeForSplit && appCanEnterPip;
|
||||
SwipePipToHomeAnimator swipePipToHomeAnimator = !mIsSwipeForSplit && appCanEnterPip
|
||||
? createWindowAnimationToPip(homeAnimFactory, runningTaskTarget, start)
|
||||
: null;
|
||||
mIsSwipingPipToHome = swipePipToHomeAnimator != null;
|
||||
final RectFSpringAnim[] windowAnim;
|
||||
if (mIsSwipingPipToHome) {
|
||||
mSwipePipToHomeAnimator = createWindowAnimationToPip(
|
||||
homeAnimFactory, runningTaskTarget, start);
|
||||
mSwipePipToHomeAnimator = swipePipToHomeAnimator;
|
||||
mSwipePipToHomeAnimators[0] = mSwipePipToHomeAnimator;
|
||||
if (mSwipePipToHomeReleaseCheck != null) {
|
||||
mSwipePipToHomeReleaseCheck.setCanRelease(false);
|
||||
@@ -1546,6 +1560,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
|
||||
// grab a screenshot before the PipContentOverlay gets parented on top of the task
|
||||
UI_HELPER_EXECUTOR.execute(() -> {
|
||||
if (mRecentsAnimationController == null) {
|
||||
return;
|
||||
}
|
||||
// Directly use top task, split to pip handled on shell side
|
||||
final int taskId = mGestureState.getTopRunningTaskId();
|
||||
mTaskSnapshotCache.put(taskId,
|
||||
@@ -1663,6 +1680,10 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
@Nullable
|
||||
private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
|
||||
RemoteAnimationTarget runningTaskTarget, float startProgress) {
|
||||
if (mRecentsView == null) {
|
||||
// Overview was destroyed, bail early.
|
||||
return null;
|
||||
}
|
||||
// Directly animate the app to PiP (picture-in-picture) mode
|
||||
final ActivityManager.RunningTaskInfo taskInfo = runningTaskTarget.taskInfo;
|
||||
final RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
|
||||
@@ -1803,7 +1824,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
private void continueComputingRecentsScrollIfNecessary() {
|
||||
if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)
|
||||
&& !mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)
|
||||
&& !mCanceled) {
|
||||
&& !mCanceled
|
||||
&& mRecentsView != null) {
|
||||
computeRecentsScrollIfInvisible();
|
||||
mRecentsView.postOnAnimation(this::continueComputingRecentsScrollIfNecessary);
|
||||
}
|
||||
@@ -1844,12 +1866,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
}
|
||||
|
||||
public void onConsumerAboutToBeSwitched() {
|
||||
if (mActivity != null) {
|
||||
// In the off chance that the gesture ends before Launcher is started, we should clear
|
||||
// the callback here so that it doesn't update with the wrong state
|
||||
mActivity.clearRunOnceOnStartCallback();
|
||||
resetLauncherListeners();
|
||||
}
|
||||
// In the off chance that the gesture ends before Launcher is started, we should clear
|
||||
// the callback here so that it doesn't update with the wrong state
|
||||
resetLauncherListeners();
|
||||
if (mGestureState.isRecentsAnimationRunning() && mGestureState.getEndTarget() != null
|
||||
&& !mGestureState.getEndTarget().isLauncher) {
|
||||
// Continued quick switch.
|
||||
@@ -1913,7 +1932,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
private void reset() {
|
||||
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
|
||||
if (mActivity != null) {
|
||||
mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
|
||||
mActivity.removeEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1929,7 +1948,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
mCurrentShift.cancelAnimation();
|
||||
|
||||
// Cleanup when switching handlers
|
||||
mInputConsumerProxy.unregisterCallback();
|
||||
mInputConsumerProxy.unregisterOnTouchDownCallback();
|
||||
mActivityInitListener.unregister();
|
||||
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
|
||||
mActivityRestartListener);
|
||||
@@ -1941,7 +1960,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
mInputConsumerProxy.destroy();
|
||||
mTaskAnimationManager.setLiveTileCleanUpHandler(null);
|
||||
}
|
||||
mInputConsumerProxy.unregisterCallback();
|
||||
mInputConsumerProxy.unregisterOnTouchDownCallback();
|
||||
endRunningWindowAnim(false /* cancel */);
|
||||
|
||||
if (mGestureEndCallback != null) {
|
||||
@@ -1984,8 +2003,12 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
* continued quick switch gesture, which cancels the previous handler but doesn't invalidate it.
|
||||
*/
|
||||
private void resetLauncherListeners() {
|
||||
mActivity.getRootView().setOnApplyWindowInsetsListener(null);
|
||||
if (mActivity != null) {
|
||||
mActivity.removeEventCallback(EVENT_STARTED, mLauncherOnStartCallback);
|
||||
mActivity.removeEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
|
||||
|
||||
mActivity.getRootView().setOnApplyWindowInsetsListener(null);
|
||||
}
|
||||
if (mRecentsView != null) {
|
||||
mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
|
||||
}
|
||||
@@ -2021,10 +2044,12 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
// Update the screenshot of the task
|
||||
if (shouldUpdate) {
|
||||
UI_HELPER_EXECUTOR.execute(() -> {
|
||||
if (mRecentsAnimationController == null) return;
|
||||
RecentsAnimationController recentsAnimationController =
|
||||
mRecentsAnimationController;
|
||||
if (recentsAnimationController == null) return;
|
||||
for (int id : runningTaskIds) {
|
||||
mTaskSnapshotCache.put(
|
||||
id, mRecentsAnimationController.screenshotTask(id));
|
||||
id, recentsAnimationController.screenshotTask(id));
|
||||
}
|
||||
|
||||
MAIN_EXECUTOR.execute(() -> {
|
||||
@@ -2077,18 +2102,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
}
|
||||
|
||||
private void finishCurrentTransitionToRecents() {
|
||||
if (mRecentsView != null
|
||||
&& mActivityInterface.getDesktopVisibilityController() != null
|
||||
&& mActivityInterface.getDesktopVisibilityController().areFreeformTasksVisible()) {
|
||||
mRecentsView.switchToScreenshot(() -> {
|
||||
mRecentsView.finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
|
||||
() -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
|
||||
});
|
||||
} else {
|
||||
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
|
||||
if (mRecentsAnimationController != null) {
|
||||
mRecentsAnimationController.detachNavigationBarFromApp(true);
|
||||
}
|
||||
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
|
||||
if (mRecentsAnimationController != null) {
|
||||
mRecentsAnimationController.detachNavigationBarFromApp(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2114,7 +2130,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
* from Launcher to WM.
|
||||
*/
|
||||
private void maybeAbortSwipePipToHome() {
|
||||
if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
|
||||
if (mIsSwipingPipToHome && mSwipePipToHomeAnimator != null) {
|
||||
SystemUiProxy.INSTANCE.get(mContext).abortSwipePipToHome(
|
||||
mSwipePipToHomeAnimator.getTaskId(),
|
||||
mSwipePipToHomeAnimator.getComponentName());
|
||||
@@ -2128,7 +2144,10 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
* This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
|
||||
*/
|
||||
private void maybeFinishSwipePipToHome() {
|
||||
if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
|
||||
if (mRecentsAnimationController == null) {
|
||||
return;
|
||||
}
|
||||
if (mIsSwipingPipToHome && mSwipePipToHomeAnimator != null) {
|
||||
mRecentsAnimationController.setFinishTaskTransaction(
|
||||
mSwipePipToHomeAnimator.getTaskId(),
|
||||
mSwipePipToHomeAnimator.getFinishTransaction(),
|
||||
@@ -2152,7 +2171,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
protected abstract void finishRecentsControllerToHome(Runnable callback);
|
||||
|
||||
private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
|
||||
if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
|
||||
if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED) || mRecentsView == null) {
|
||||
return;
|
||||
}
|
||||
endLauncherTransitionController();
|
||||
@@ -2186,6 +2205,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
}
|
||||
|
||||
protected void linkRecentsViewScroll() {
|
||||
if (mRecentsView == null) {
|
||||
return;
|
||||
}
|
||||
SurfaceTransactionApplier applier = new SurfaceTransactionApplier(mRecentsView);
|
||||
runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
|
||||
.setSyncTransactionApplier(applier));
|
||||
@@ -2193,9 +2215,13 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
mRecentsAnimationTargets.addReleaseCheck(applier));
|
||||
|
||||
mRecentsView.addOnScrollChangedListener(mOnRecentsScrollListener);
|
||||
runOnRecentsAnimationAndLauncherBound(() ->
|
||||
mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
|
||||
mRecentsAnimationTargets));
|
||||
runOnRecentsAnimationAndLauncherBound(() -> {
|
||||
if (mRecentsView == null) {
|
||||
return;
|
||||
}
|
||||
mRecentsView.setRecentsAnimationTargets(
|
||||
mRecentsAnimationController, mRecentsAnimationTargets);
|
||||
});
|
||||
|
||||
// Disable scrolling in RecentsView for trackpad 3-finger swipe up gesture.
|
||||
if (!mGestureState.isThreeFingerTrackpadGesture()) {
|
||||
@@ -2215,7 +2241,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
TaskView nextTask = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
|
||||
if (nextTask != null) {
|
||||
int[] taskIds = nextTask.getTaskIds();
|
||||
StringBuilder nextTaskLog = new StringBuilder();
|
||||
ActiveGestureLog.CompoundString nextTaskLog = new ActiveGestureLog.CompoundString(
|
||||
"Launching task: ");
|
||||
for (TaskIdAttributeContainer c : nextTask.getTaskIdAttributeContainers()) {
|
||||
if (c == null) {
|
||||
continue;
|
||||
@@ -2234,7 +2261,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
if (!hasTaskPreviouslyAppeared) {
|
||||
ActiveGestureLog.INSTANCE.trackEvent(EXPECTING_TASK_APPEARED);
|
||||
}
|
||||
ActiveGestureLog.INSTANCE.addLog("Launching task: " + nextTaskLog);
|
||||
ActiveGestureLog.INSTANCE.addLog(nextTaskLog);
|
||||
nextTask.launchTask(success -> {
|
||||
resultCallback.accept(success);
|
||||
if (success) {
|
||||
@@ -2292,7 +2319,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
|
||||
public void onRecentsAnimationFinished(@NonNull RecentsAnimationController controller) {
|
||||
mRecentsAnimationController = null;
|
||||
mRecentsAnimationTargets = null;
|
||||
if (mRecentsView != null) {
|
||||
@@ -2301,77 +2328,95 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
|
||||
if (mRecentsAnimationController != null) {
|
||||
boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTargets).anyMatch(
|
||||
mGestureState.mLastStartedTaskIdPredicate);
|
||||
if (!mStateCallback.hasStates(STATE_GESTURE_COMPLETED) && !hasStartedTaskBefore) {
|
||||
// This is a special case, if a task is started mid-gesture that wasn't a part of a
|
||||
// previous quickswitch task launch, then cancel the animation back to the app
|
||||
RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
|
||||
TaskInfo taskInfo = appearedTaskTarget.taskInfo;
|
||||
ActiveGestureLog.INSTANCE.addLog("Unexpected task appeared"
|
||||
+ " id=" + taskInfo.taskId
|
||||
+ " pkg=" + taskInfo.baseIntent.getComponent().getPackageName());
|
||||
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
|
||||
} else if (handleTaskAppeared(appearedTaskTargets)) {
|
||||
Optional<RemoteAnimationTarget> taskTargetOptional =
|
||||
Arrays.stream(appearedTaskTargets)
|
||||
.filter(mGestureState.mLastStartedTaskIdPredicate)
|
||||
.findFirst();
|
||||
if (!taskTargetOptional.isPresent()) {
|
||||
ActiveGestureLog.INSTANCE.addLog("No appeared task matching started task id");
|
||||
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
|
||||
return;
|
||||
}
|
||||
RemoteAnimationTarget taskTarget = taskTargetOptional.get();
|
||||
TaskView taskView = mRecentsView == null
|
||||
? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
|
||||
if (taskView == null || !taskView.getThumbnail().shouldShowSplashView()) {
|
||||
ActiveGestureLog.INSTANCE.addLog("Invalid task view splash state");
|
||||
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
|
||||
return;
|
||||
}
|
||||
|
||||
ViewGroup splashView = mActivity.getDragLayer();
|
||||
final QuickstepLauncher quickstepLauncher = mActivity instanceof QuickstepLauncher
|
||||
? (QuickstepLauncher) mActivity : null;
|
||||
if (quickstepLauncher != null) {
|
||||
quickstepLauncher.getDepthController().pauseBlursOnWindows(true);
|
||||
}
|
||||
|
||||
// When revealing the app with launcher splash screen, make the app visible
|
||||
// and behind the splash view before the splash is animated away.
|
||||
SurfaceTransactionApplier surfaceApplier =
|
||||
new SurfaceTransactionApplier(splashView);
|
||||
SurfaceTransaction transaction = new SurfaceTransaction();
|
||||
for (RemoteAnimationTarget target : appearedTaskTargets) {
|
||||
transaction.forSurface(target.leash).setAlpha(1).setLayer(-1).setShow();
|
||||
}
|
||||
surfaceApplier.scheduleApply(transaction);
|
||||
|
||||
SplashScreenExitAnimationUtils.startAnimations(splashView, taskTarget.leash,
|
||||
mSplashMainWindowShiftLength, new TransactionPool(), new Rect(),
|
||||
SPLASH_ANIMATION_DURATION, SPLASH_FADE_OUT_DURATION,
|
||||
/* iconStartAlpha= */ 0, /* brandingStartAlpha= */ 0,
|
||||
SPLASH_APP_REVEAL_DELAY, SPLASH_APP_REVEAL_DURATION,
|
||||
new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
// Hiding launcher which shows the app surface behind, then
|
||||
// finishing recents to the app. After transition finish, showing
|
||||
// the views on launcher again, so it can be visible when next
|
||||
// animation starts.
|
||||
splashView.setAlpha(0);
|
||||
if (quickstepLauncher != null) {
|
||||
quickstepLauncher.getDepthController()
|
||||
.pauseBlursOnWindows(false);
|
||||
}
|
||||
finishRecentsAnimationOnTasksAppeared(() -> splashView.setAlpha(1));
|
||||
}
|
||||
});
|
||||
}
|
||||
public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets) {
|
||||
if (mRecentsAnimationController == null) {
|
||||
return;
|
||||
}
|
||||
boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTargets).anyMatch(
|
||||
mGestureState.mLastStartedTaskIdPredicate);
|
||||
if (!mStateCallback.hasStates(STATE_GESTURE_COMPLETED) && !hasStartedTaskBefore) {
|
||||
// This is a special case, if a task is started mid-gesture that wasn't a part of a
|
||||
// previous quickswitch task launch, then cancel the animation back to the app
|
||||
RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
|
||||
TaskInfo taskInfo = appearedTaskTarget.taskInfo;
|
||||
ActiveGestureLog.INSTANCE.addLog(
|
||||
new ActiveGestureLog.CompoundString("Unexpected task appeared")
|
||||
.append(" id=")
|
||||
.append(taskInfo.taskId)
|
||||
.append(" pkg=")
|
||||
.append(taskInfo.baseIntent.getComponent().getPackageName()));
|
||||
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
|
||||
return;
|
||||
}
|
||||
if (!handleTaskAppeared(appearedTaskTargets)) {
|
||||
return;
|
||||
}
|
||||
Optional<RemoteAnimationTarget> taskTargetOptional =
|
||||
Arrays.stream(appearedTaskTargets)
|
||||
.filter(mGestureState.mLastStartedTaskIdPredicate)
|
||||
.findFirst();
|
||||
if (!taskTargetOptional.isPresent()) {
|
||||
ActiveGestureLog.INSTANCE.addLog("No appeared task matching started task id");
|
||||
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
|
||||
return;
|
||||
}
|
||||
RemoteAnimationTarget taskTarget = taskTargetOptional.get();
|
||||
TaskView taskView = mRecentsView == null
|
||||
? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
|
||||
if (taskView == null || !taskView.getThumbnail().shouldShowSplashView()) {
|
||||
ActiveGestureLog.INSTANCE.addLog("Invalid task view splash state");
|
||||
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
|
||||
return;
|
||||
}
|
||||
if (mActivity == null) {
|
||||
ActiveGestureLog.INSTANCE.addLog("Activity destroyed");
|
||||
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
|
||||
return;
|
||||
}
|
||||
animateSplashScreenExit(mActivity, appearedTaskTargets, taskTarget.leash);
|
||||
}
|
||||
|
||||
private void animateSplashScreenExit(
|
||||
@NonNull T activity,
|
||||
@NonNull RemoteAnimationTarget[] appearedTaskTargets,
|
||||
@NonNull SurfaceControl leash) {
|
||||
ViewGroup splashView = activity.getDragLayer();
|
||||
final QuickstepLauncher quickstepLauncher = activity instanceof QuickstepLauncher
|
||||
? (QuickstepLauncher) activity : null;
|
||||
if (quickstepLauncher != null) {
|
||||
quickstepLauncher.getDepthController().pauseBlursOnWindows(true);
|
||||
}
|
||||
|
||||
// When revealing the app with launcher splash screen, make the app visible
|
||||
// and behind the splash view before the splash is animated away.
|
||||
SurfaceTransactionApplier surfaceApplier =
|
||||
new SurfaceTransactionApplier(splashView);
|
||||
SurfaceTransaction transaction = new SurfaceTransaction();
|
||||
for (RemoteAnimationTarget target : appearedTaskTargets) {
|
||||
transaction.forSurface(target.leash).setAlpha(1).setLayer(-1).setShow();
|
||||
}
|
||||
surfaceApplier.scheduleApply(transaction);
|
||||
|
||||
SplashScreenExitAnimationUtils.startAnimations(splashView, leash,
|
||||
mSplashMainWindowShiftLength, new TransactionPool(), new Rect(),
|
||||
SPLASH_ANIMATION_DURATION, SPLASH_FADE_OUT_DURATION,
|
||||
/* iconStartAlpha= */ 0, /* brandingStartAlpha= */ 0,
|
||||
SPLASH_APP_REVEAL_DELAY, SPLASH_APP_REVEAL_DURATION,
|
||||
new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
// Hiding launcher which shows the app surface behind, then
|
||||
// finishing recents to the app. After transition finish, showing
|
||||
// the views on launcher again, so it can be visible when next
|
||||
// animation starts.
|
||||
splashView.setAlpha(0);
|
||||
if (quickstepLauncher != null) {
|
||||
quickstepLauncher.getDepthController()
|
||||
.pauseBlursOnWindows(false);
|
||||
}
|
||||
finishRecentsAnimationOnTasksAppeared(() -> splashView.setAlpha(1));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void finishRecentsAnimationOnTasksAppeared(Runnable onFinishComplete) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
|
||||
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
|
||||
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
|
||||
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
|
||||
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
|
||||
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
|
||||
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
|
||||
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
|
||||
@@ -50,10 +51,10 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.statehandlers.DepthController;
|
||||
import com.android.launcher3.statehandlers.DesktopVisibilityController;
|
||||
import com.android.launcher3.statemanager.BaseState;
|
||||
@@ -65,7 +66,6 @@ import com.android.launcher3.util.NavigationMode;
|
||||
import com.android.launcher3.views.ScrimView;
|
||||
import com.android.quickstep.util.ActivityInitListener;
|
||||
import com.android.quickstep.util.AnimatorControllerWithResistance;
|
||||
import com.android.quickstep.views.DesktopTaskView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
|
||||
@@ -111,7 +111,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
if (endTarget != null) {
|
||||
// We were on our way to this state when we got canceled, end there instead.
|
||||
startState = stateFromGestureEndTarget(endTarget);
|
||||
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
|
||||
if (isDesktopModeSupported()) {
|
||||
DesktopVisibilityController controller = getDesktopVisibilityController();
|
||||
if (controller != null && controller.areFreeformTasksVisible()
|
||||
&& endTarget == LAST_TASK) {
|
||||
@@ -191,8 +191,11 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
public abstract boolean allowAllAppsFromOverview();
|
||||
|
||||
public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
|
||||
TaskbarUIController controller = getTaskbarController();
|
||||
boolean isEventOverBubbleBarStashHandle =
|
||||
controller != null && controller.isEventOverBubbleBarStashHandle(ev);
|
||||
return deviceState.isInDeferredGestureRegion(ev) || deviceState.isImeRenderingNavButtons()
|
||||
|| isTrackpadMultiFingerSwipe(ev);
|
||||
|| isTrackpadMultiFingerSwipe(ev) || isEventOverBubbleBarStashHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,7 +242,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
|
||||
PagedOrientationHandler orientedState) {
|
||||
if (dp.isTablet) {
|
||||
if (FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) {
|
||||
if (Flags.enableGridOnlyOverview()) {
|
||||
calculateGridTaskSize(context, dp, outRect, orientedState);
|
||||
} else {
|
||||
calculateFocusTaskSize(context, dp, outRect);
|
||||
@@ -264,7 +267,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
Resources res = context.getResources();
|
||||
float maxScale = res.getFloat(R.dimen.overview_max_scale);
|
||||
Rect gridRect = new Rect();
|
||||
calculateGridSize(dp, gridRect);
|
||||
calculateGridSize(dp, context, gridRect);
|
||||
calculateTaskSizeInternal(context, dp, gridRect, maxScale, Gravity.CENTER, outRect);
|
||||
}
|
||||
|
||||
@@ -318,10 +321,16 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
/**
|
||||
* Calculates the overview grid size for the provided device configuration.
|
||||
*/
|
||||
public final void calculateGridSize(DeviceProfile dp, Rect outRect) {
|
||||
public final void calculateGridSize(DeviceProfile dp, Context context, Rect outRect) {
|
||||
Rect insets = dp.getInsets();
|
||||
int topMargin = dp.overviewTaskThumbnailTopMarginPx;
|
||||
int bottomMargin = dp.getOverviewActionsClaimedSpace();
|
||||
if (dp.isTaskbarPresent && Flags.enableGridOnlyOverview()) {
|
||||
topMargin += context.getResources().getDimensionPixelSize(
|
||||
R.dimen.overview_top_margin_grid_only);
|
||||
bottomMargin += context.getResources().getDimensionPixelSize(
|
||||
R.dimen.overview_bottom_margin_grid_only);
|
||||
}
|
||||
int sideMargin = dp.overviewGridSideMargin;
|
||||
|
||||
outRect.set(0, 0, dp.widthPx, dp.heightPx);
|
||||
@@ -336,8 +345,8 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
PagedOrientationHandler orientedState) {
|
||||
Resources res = context.getResources();
|
||||
Rect potentialTaskRect = new Rect();
|
||||
if (FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) {
|
||||
calculateGridSize(dp, potentialTaskRect);
|
||||
if (Flags.enableGridOnlyOverview()) {
|
||||
calculateGridSize(dp, context, potentialTaskRect);
|
||||
} else {
|
||||
calculateFocusTaskSize(context, dp, potentialTaskRect);
|
||||
}
|
||||
@@ -368,7 +377,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect,
|
||||
PagedOrientationHandler orientedState) {
|
||||
calculateTaskSize(context, dp, outRect, orientedState);
|
||||
boolean isGridOnlyOverview = dp.isTablet && FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get();
|
||||
boolean isGridOnlyOverview = dp.isTablet && Flags.enableGridOnlyOverview();
|
||||
int claimedSpaceBelow = isGridOnlyOverview
|
||||
? dp.overviewActionsTopMarginPx + dp.overviewActionsHeight + dp.stashedTaskbarHeight
|
||||
: (dp.heightPx - outRect.bottom - dp.getInsets().bottom);
|
||||
@@ -437,11 +446,13 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
|
||||
}
|
||||
|
||||
protected void runOnInitBackgroundStateUI(Runnable callback) {
|
||||
mOnInitBackgroundStateUICallback = callback;
|
||||
ACTIVITY_TYPE activity = getCreatedActivity();
|
||||
if (activity != null && activity.getStateManager().getState() == mBackgroundState) {
|
||||
callback.run();
|
||||
onInitBackgroundStateUI();
|
||||
return;
|
||||
}
|
||||
mOnInitBackgroundStateUICallback = callback;
|
||||
}
|
||||
|
||||
private void onInitBackgroundStateUI() {
|
||||
|
||||
@@ -19,7 +19,7 @@ import android.content.Context
|
||||
import android.util.Log
|
||||
import com.android.launcher3.LauncherAppState
|
||||
import com.android.launcher3.LauncherPrefs
|
||||
import com.android.launcher3.isBootAwareStartupDataEnabled
|
||||
import com.android.launcher3.moveStartupDataToDeviceProtectedStorageIsEnabled
|
||||
import com.android.launcher3.util.LockedUserState
|
||||
|
||||
/**
|
||||
@@ -33,7 +33,8 @@ object BootAwarePreloader {
|
||||
fun start(context: Context) {
|
||||
val lp = LauncherPrefs.get(context)
|
||||
when {
|
||||
LockedUserState.get(context).isUserUnlocked || !isBootAwareStartupDataEnabled -> {
|
||||
LockedUserState.get(context).isUserUnlocked ||
|
||||
!moveStartupDataToDeviceProtectedStorageIsEnabled -> {
|
||||
/* No-Op */
|
||||
}
|
||||
lp.isStartupDataMigrated -> {
|
||||
|
||||
@@ -66,6 +66,7 @@ import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.quickstep.fallback.FallbackRecentsView;
|
||||
import com.android.quickstep.fallback.RecentsState;
|
||||
import com.android.quickstep.util.ActiveGestureLog;
|
||||
import com.android.quickstep.util.RectFSpringAnim;
|
||||
import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
|
||||
import com.android.quickstep.util.TransformParams;
|
||||
@@ -151,19 +152,20 @@ public class FallbackSwipeHandler extends
|
||||
return new FallbackPipToHomeAnimationFactory();
|
||||
}
|
||||
mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
|
||||
startHomeIntent(mActiveAnimationFactory, runningTaskTarget);
|
||||
startHomeIntent(mActiveAnimationFactory, runningTaskTarget, "FallbackSwipeHandler-home");
|
||||
return mActiveAnimationFactory;
|
||||
}
|
||||
|
||||
private void startHomeIntent(
|
||||
@Nullable FallbackHomeAnimationFactory gestureContractAnimationFactory,
|
||||
@Nullable RemoteAnimationTarget runningTaskTarget) {
|
||||
@Nullable RemoteAnimationTarget runningTaskTarget,
|
||||
@NonNull String reason) {
|
||||
ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
|
||||
Intent intent = new Intent(mGestureState.getHomeIntent());
|
||||
if (gestureContractAnimationFactory != null && runningTaskTarget != null) {
|
||||
gestureContractAnimationFactory.addGestureContract(intent, runningTaskTarget.taskInfo);
|
||||
}
|
||||
startHomeIntentSafely(mContext, intent, options.toBundle());
|
||||
startHomeIntentSafely(mContext, intent, options.toBundle(), reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -185,8 +187,8 @@ public class FallbackSwipeHandler extends
|
||||
// the PiP task appearing.
|
||||
recentsCallback = () -> {
|
||||
callback.run();
|
||||
startHomeIntent(
|
||||
null /* gestureContractAnimationFactory */, null /* runningTaskTarget */);
|
||||
startHomeIntent(null /* gestureContractAnimationFactory */,
|
||||
null /* runningTaskTarget */, "FallbackSwipeHandler-resumeLauncher");
|
||||
};
|
||||
} else {
|
||||
recentsCallback = callback;
|
||||
|
||||
@@ -29,13 +29,15 @@ import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent
|
||||
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_HOME;
|
||||
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_NEW_TASK;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.RemoteAnimationTarget;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.statemanager.BaseState;
|
||||
import com.android.launcher3.statemanager.StatefulActivity;
|
||||
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
|
||||
@@ -303,6 +305,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
/**
|
||||
* @return the running task for this gesture.
|
||||
*/
|
||||
@Nullable
|
||||
public CachedTaskInfo getRunningTask() {
|
||||
return mRunningTask;
|
||||
}
|
||||
@@ -336,7 +339,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
/**
|
||||
* Updates the running task for the gesture to be the given {@param runningTask}.
|
||||
*/
|
||||
public void updateRunningTask(CachedTaskInfo runningTask) {
|
||||
public void updateRunningTask(@NonNull CachedTaskInfo runningTask) {
|
||||
mRunningTask = runningTask;
|
||||
}
|
||||
|
||||
@@ -410,7 +413,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
|
||||
mEndTarget = target;
|
||||
mStateCallback.setState(STATE_END_TARGET_SET);
|
||||
ActiveGestureLog.INSTANCE.addLog(
|
||||
/* event= */ "setEndTarget " + mEndTarget,
|
||||
new ActiveGestureLog.CompoundString("setEndTarget ")
|
||||
.append(mEndTarget.name()),
|
||||
/* gestureEvent= */ SET_END_TARGET);
|
||||
switch (mEndTarget) {
|
||||
case HOME:
|
||||
|
||||
@@ -126,4 +126,14 @@ public interface InputConsumer {
|
||||
}
|
||||
return name.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an input consumer of the given class type, or null if none is found.
|
||||
*/
|
||||
default <T extends InputConsumer> T getInputConsumerOfClass(Class<T> c) {
|
||||
if (getClass().equals(c)) {
|
||||
return c.cast(this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,8 +276,11 @@ public final class LauncherActivityInterface extends
|
||||
@Override
|
||||
public boolean isInLiveTileMode() {
|
||||
Launcher launcher = getCreatedActivity();
|
||||
return launcher != null && launcher.getStateManager().getState() == OVERVIEW &&
|
||||
launcher.isStarted();
|
||||
|
||||
return launcher != null
|
||||
&& launcher.getStateManager().getState() == OVERVIEW
|
||||
&& launcher.isStarted()
|
||||
&& TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false).isHomeTask();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,6 +23,8 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
|
||||
import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
|
||||
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
|
||||
import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
|
||||
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
|
||||
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
@@ -43,6 +45,7 @@ import android.view.SurfaceControl;
|
||||
import android.view.View;
|
||||
import android.view.ViewRootImpl;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.window.BackEvent;
|
||||
import android.window.BackMotionEvent;
|
||||
@@ -51,13 +54,17 @@ import android.window.IOnBackInvokedCallback;
|
||||
|
||||
import com.android.internal.view.AppearanceRegion;
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.QuickstepTransitionManager;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.quickstep.util.RectFSpringAnim;
|
||||
import com.android.systemui.shared.system.QuickStepContract;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* Controls the animation of swiping back and returning to launcher.
|
||||
*
|
||||
@@ -97,6 +104,7 @@ public class LauncherBackAnimationController {
|
||||
private final float mWindowScaleEndCornerRadius;
|
||||
private final float mWindowScaleStartCornerRadius;
|
||||
private final Interpolator mCancelInterpolator;
|
||||
private final Interpolator mProgressInterpolator = new DecelerateInterpolator();
|
||||
private final PointF mInitialTouchPos = new PointF();
|
||||
|
||||
private RemoteAnimationTarget mBackTarget;
|
||||
@@ -105,7 +113,7 @@ public class LauncherBackAnimationController {
|
||||
private boolean mAnimatorSetInProgress = false;
|
||||
private float mBackProgress = 0;
|
||||
private boolean mBackInProgress = false;
|
||||
private IOnBackInvokedCallback mBackCallback;
|
||||
private OnBackInvokedCallbackStub mBackCallback;
|
||||
private IRemoteAnimationFinishedCallback mAnimationFinishedCallback;
|
||||
private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
|
||||
private SurfaceControl mScrimLayer;
|
||||
@@ -137,65 +145,113 @@ public class LauncherBackAnimationController {
|
||||
* @param handler Handler to the thread to run the animations on.
|
||||
*/
|
||||
public void registerBackCallbacks(Handler handler) {
|
||||
mBackCallback = new IOnBackInvokedCallback.Stub() {
|
||||
@Override
|
||||
public void onBackCancelled() {
|
||||
handler.post(() -> {
|
||||
mBackCallback = new OnBackInvokedCallbackStub(handler, mProgressAnimator,
|
||||
mProgressInterpolator, this);
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(mBackCallback,
|
||||
new RemoteAnimationRunnerStub(this));
|
||||
}
|
||||
|
||||
private static class OnBackInvokedCallbackStub extends IOnBackInvokedCallback.Stub {
|
||||
private Handler mHandler;
|
||||
private BackProgressAnimator mProgressAnimator;
|
||||
private final Interpolator mProgressInterpolator;
|
||||
// LauncherBackAnimationController has strong reference to Launcher activity, the binder
|
||||
// callback should not hold strong reference to it to avoid memory leak.
|
||||
private WeakReference<LauncherBackAnimationController> mControllerRef;
|
||||
|
||||
private OnBackInvokedCallbackStub(
|
||||
Handler handler,
|
||||
BackProgressAnimator progressAnimator,
|
||||
Interpolator progressInterpolator,
|
||||
LauncherBackAnimationController controller) {
|
||||
mHandler = handler;
|
||||
mProgressAnimator = progressAnimator;
|
||||
mProgressInterpolator = progressInterpolator;
|
||||
mControllerRef = new WeakReference<>(controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackCancelled() {
|
||||
mHandler.post(() -> {
|
||||
LauncherBackAnimationController controller = mControllerRef.get();
|
||||
if (controller != null) {
|
||||
mProgressAnimator.onBackCancelled(
|
||||
LauncherBackAnimationController.this::resetPositionAnimated);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackInvoked() {
|
||||
handler.post(() -> {
|
||||
startTransition();
|
||||
mProgressAnimator.reset();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackProgressed(BackMotionEvent backEvent) {
|
||||
handler.post(() -> {
|
||||
mProgressAnimator.onBackProgressed(backEvent);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStarted(BackMotionEvent backEvent) {
|
||||
handler.post(() -> {
|
||||
startBack(backEvent);
|
||||
mProgressAnimator.onBackStarted(backEvent, event -> {
|
||||
mBackProgress = event.getProgress();
|
||||
// TODO: Update once the interpolation curve spec is finalized.
|
||||
mBackProgress =
|
||||
1 - (1 - mBackProgress) * (1 - mBackProgress) * (1
|
||||
- mBackProgress);
|
||||
updateBackProgress(mBackProgress, event);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
final IRemoteAnimationRunner runner = new IRemoteAnimationRunner.Stub() {
|
||||
@Override
|
||||
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
|
||||
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
|
||||
IRemoteAnimationFinishedCallback finishedCallback) {
|
||||
for (final RemoteAnimationTarget target : apps) {
|
||||
if (MODE_CLOSING == target.mode) {
|
||||
mBackTarget = target;
|
||||
break;
|
||||
}
|
||||
controller::resetPositionAnimated);
|
||||
}
|
||||
mAnimationFinishedCallback = finishedCallback;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackInvoked() {
|
||||
mHandler.post(() -> {
|
||||
LauncherBackAnimationController controller = mControllerRef.get();
|
||||
if (controller != null) {
|
||||
controller.startTransition();
|
||||
}
|
||||
mProgressAnimator.reset();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackProgressed(BackMotionEvent backMotionEvent) {
|
||||
mHandler.post(() -> {
|
||||
LauncherBackAnimationController controller = mControllerRef.get();
|
||||
if (controller == null
|
||||
|| controller.mLauncher == null
|
||||
|| !controller.mLauncher.isStarted()) {
|
||||
// Skip animating back progress if Launcher isn't visible yet.
|
||||
return;
|
||||
}
|
||||
mProgressAnimator.onBackProgressed(backMotionEvent);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStarted(BackMotionEvent backEvent) {
|
||||
mHandler.post(() -> {
|
||||
LauncherBackAnimationController controller = mControllerRef.get();
|
||||
if (controller != null) {
|
||||
controller.startBack(backEvent);
|
||||
mProgressAnimator.onBackStarted(backEvent, event -> {
|
||||
float backProgress = event.getProgress();
|
||||
controller.mBackProgress =
|
||||
mProgressInterpolator.getInterpolation(backProgress);
|
||||
controller.updateBackProgress(controller.mBackProgress, event);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemoteAnimationRunnerStub extends IRemoteAnimationRunner.Stub {
|
||||
|
||||
// LauncherBackAnimationController has strong reference to Launcher activity, the binder
|
||||
// callback should not hold strong reference to it to avoid memory leak.
|
||||
private WeakReference<LauncherBackAnimationController> mControllerRef;
|
||||
|
||||
private RemoteAnimationRunnerStub(LauncherBackAnimationController controller) {
|
||||
mControllerRef = new WeakReference<>(controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
|
||||
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
|
||||
IRemoteAnimationFinishedCallback finishedCallback) {
|
||||
LauncherBackAnimationController controller = mControllerRef.get();
|
||||
if (controller == null) {
|
||||
return;
|
||||
}
|
||||
for (final RemoteAnimationTarget target : apps) {
|
||||
if (MODE_CLOSING == target.mode) {
|
||||
controller.mBackTarget = target;
|
||||
break;
|
||||
}
|
||||
}
|
||||
controller.mAnimationFinishedCallback = finishedCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancelled() {}
|
||||
};
|
||||
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(mBackCallback, runner);
|
||||
@Override
|
||||
public void onAnimationCancelled() {}
|
||||
}
|
||||
|
||||
private void resetPositionAnimated() {
|
||||
@@ -241,8 +297,12 @@ public class LauncherBackAnimationController {
|
||||
mBackTarget = appTarget;
|
||||
mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
|
||||
|
||||
// TODO(b/218916755): Offset start rectangle in multiwindow mode.
|
||||
mStartRect.set(appTarget.windowConfiguration.getMaxBounds());
|
||||
if (mLauncher.getDeviceProfile().isTaskbarPresent && enableTaskbarPinning()
|
||||
&& LauncherPrefs.get(mLauncher).get(TASKBAR_PINNING)) {
|
||||
int insetBottom = mStartRect.bottom - appTarget.contentInsets.bottom;
|
||||
mStartRect.set(mStartRect.left, mStartRect.top, mStartRect.right, insetBottom);
|
||||
}
|
||||
mCurrentRect.set(mStartRect);
|
||||
addScrimLayer();
|
||||
mTransaction.apply();
|
||||
@@ -253,6 +313,10 @@ public class LauncherBackAnimationController {
|
||||
SurfaceControl parent = viewRootImpl != null
|
||||
? viewRootImpl.getSurfaceControl()
|
||||
: null;
|
||||
if (parent == null || !parent.isValid()) {
|
||||
// Parent surface is not ready at the moment. Retry later.
|
||||
return;
|
||||
}
|
||||
boolean isDarkTheme = Utilities.isDarkTheme(mLauncher);
|
||||
mScrimLayer = new SurfaceControl.Builder()
|
||||
.setName("Back to launcher background scrim")
|
||||
@@ -285,13 +349,24 @@ public class LauncherBackAnimationController {
|
||||
if (!mBackInProgress || mBackTarget == null) {
|
||||
return;
|
||||
}
|
||||
if (mScrimLayer == null) {
|
||||
// Scrim hasn't been attached yet. Let's attach it.
|
||||
addScrimLayer();
|
||||
}
|
||||
float screenWidth = mStartRect.width();
|
||||
float screenHeight = mStartRect.height();
|
||||
float width = Utilities.mapRange(progress, 1, MIN_WINDOW_SCALE) * screenWidth;
|
||||
float height = screenHeight / screenWidth * width;
|
||||
float deltaYRatio = (event.getTouchY() - mInitialTouchPos.y) / screenHeight;
|
||||
|
||||
// Base the window movement in the Y axis on the touch movement in the Y axis.
|
||||
float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * mWindowMaxDeltaY * progress;
|
||||
float rawYDelta = event.getTouchY() - mInitialTouchPos.y;
|
||||
float yDirection = rawYDelta < 0 ? -1 : 1;
|
||||
// limit yDelta interpretation to 1/2 of screen height in either direction
|
||||
float deltaYRatio = Math.min(screenHeight / 2f, Math.abs(rawYDelta)) / (screenHeight / 2f);
|
||||
float interpolatedYRatio = mProgressInterpolator.getInterpolation(deltaYRatio);
|
||||
// limit y-shift so surface never passes 8dp screen margin
|
||||
float deltaY = yDirection * interpolatedYRatio * Math.max(0f, (screenHeight - height)
|
||||
/ 2f - mWindowScaleMarginX);
|
||||
// Move the window along the Y axis.
|
||||
float top = (screenHeight - height) * 0.5f + deltaY;
|
||||
// Move the window along the X axis.
|
||||
@@ -349,6 +424,10 @@ public class LauncherBackAnimationController {
|
||||
if (mLauncher.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
LauncherTaskbarUIController taskbarUIController = mLauncher.getTaskbarUIController();
|
||||
if (taskbarUIController != null) {
|
||||
taskbarUIController.onLauncherVisibilityChanged(true);
|
||||
}
|
||||
// TODO: Catch the moment when launcher becomes visible after the top app un-occludes
|
||||
// launcher and start animating afterwards. Currently we occasionally get a flicker from
|
||||
// animating when launcher is still invisible.
|
||||
@@ -363,12 +442,16 @@ public class LauncherBackAnimationController {
|
||||
AbstractFloatingView.closeAllOpenViewsExcept(mLauncher, false, TYPE_REBIND_SAFE);
|
||||
float cornerRadius = Utilities.mapRange(
|
||||
mBackProgress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius);
|
||||
final RectF resolveRectF = new RectF();
|
||||
mQuickstepTransitionManager.transferRectToTargetCoordinate(
|
||||
mBackTarget, mCurrentRect, true, resolveRectF);
|
||||
|
||||
Pair<RectFSpringAnim, AnimatorSet> pair =
|
||||
mQuickstepTransitionManager.createWallpaperOpenAnimations(
|
||||
new RemoteAnimationTarget[]{mBackTarget},
|
||||
new RemoteAnimationTarget[0],
|
||||
false /* fromUnlock */,
|
||||
mCurrentRect,
|
||||
resolveRectF,
|
||||
cornerRadius,
|
||||
mBackInProgress /* fromPredictiveBack */);
|
||||
startTransitionAnimations(pair.first, pair.second);
|
||||
@@ -401,6 +484,9 @@ public class LauncherBackAnimationController {
|
||||
mScrimAlphaAnimator.cancel();
|
||||
mScrimAlphaAnimator = null;
|
||||
}
|
||||
if (mScrimLayer != null) {
|
||||
removeScrimLayer();
|
||||
}
|
||||
}
|
||||
|
||||
private void startTransitionAnimations(RectFSpringAnim springAnim, AnimatorSet anim) {
|
||||
@@ -424,10 +510,14 @@ public class LauncherBackAnimationController {
|
||||
tryFinishBackAnimation();
|
||||
}
|
||||
});
|
||||
if (mScrimLayer == null) {
|
||||
// Scrim hasn't been attached yet. Let's attach it.
|
||||
addScrimLayer();
|
||||
}
|
||||
mScrimAlphaAnimator = new ValueAnimator().ofFloat(1, 0);
|
||||
mScrimAlphaAnimator.addUpdateListener(animation -> {
|
||||
float value = (Float) animation.getAnimatedValue();
|
||||
if (mScrimLayer.isValid()) {
|
||||
if (mScrimLayer != null && mScrimLayer.isValid()) {
|
||||
mTransaction.setAlpha(mScrimLayer, value * mScrimAlpha);
|
||||
mTransaction.apply();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.android.quickstep
|
||||
|
||||
import android.app.backup.BackupManager
|
||||
import android.app.backup.BackupRestoreEventLogger
|
||||
import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType
|
||||
import android.app.backup.BackupRestoreEventLogger.BackupRestoreError
|
||||
import android.content.Context
|
||||
import com.android.launcher3.Flags
|
||||
import com.android.launcher3.LauncherSettings.Favorites
|
||||
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger
|
||||
|
||||
/**
|
||||
* Concrete implementation for wrapper to log Restore event metrics for both success and failure to
|
||||
* restore Launcher workspace from a backup. This implementation accesses SystemApis so is only
|
||||
* available to QuickStep/NexusLauncher.
|
||||
*/
|
||||
class LauncherRestoreEventLoggerImpl(val context: Context) : LauncherRestoreEventLogger() {
|
||||
companion object {
|
||||
const val TAG = "LauncherRestoreEventLoggerImpl"
|
||||
|
||||
// Generic type for any possible workspace items, when specific type is not known.
|
||||
@BackupRestoreDataType private const val DATA_TYPE_LAUNCHER_ITEM = "launcher_item"
|
||||
// Specific workspace item types, based off of Favorites Table.
|
||||
@BackupRestoreDataType private const val DATA_TYPE_APPLICATION = "application"
|
||||
@BackupRestoreDataType private const val DATA_TYPE_FOLDER = "folder"
|
||||
@BackupRestoreDataType private const val DATA_TYPE_APPWIDGET = "widget"
|
||||
@BackupRestoreDataType private const val DATA_TYPE_CUSTOM_APPWIDGET = "custom_widget"
|
||||
@BackupRestoreDataType private const val DATA_TYPE_DEEP_SHORTCUT = "deep_shortcut"
|
||||
@BackupRestoreDataType private const val DATA_TYPE_APP_PAIR = "app_pair"
|
||||
}
|
||||
|
||||
private val backupManager: BackupManager = BackupManager(context)
|
||||
private val restoreEventLogger: BackupRestoreEventLogger = backupManager.delayedRestoreLogger
|
||||
|
||||
/**
|
||||
* For logging when multiple items of a given data type failed to restore.
|
||||
*
|
||||
* @param dataType The data type that was not restored.
|
||||
* @param count the number of data items that were not restored.
|
||||
* @param error error type for why the data was not restored.
|
||||
*/
|
||||
override fun logLauncherItemsRestoreFailed(
|
||||
@BackupRestoreDataType dataType: String,
|
||||
count: Int,
|
||||
@BackupRestoreError error: String?
|
||||
) {
|
||||
if (Flags.enableLauncherBrMetrics()) {
|
||||
restoreEventLogger.logItemsRestoreFailed(dataType, count, error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For logging when multiple items of a given data type were successfully restored.
|
||||
*
|
||||
* @param dataType The data type that was restored.
|
||||
* @param count the number of data items restored.
|
||||
*/
|
||||
override fun logLauncherItemsRestored(@BackupRestoreDataType dataType: String, count: Int) {
|
||||
if (Flags.enableLauncherBrMetrics()) {
|
||||
restoreEventLogger.logItemsRestored(dataType, count)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to log successfully restoring a single item from the Favorites table.
|
||||
*
|
||||
* @param favoritesId The id of the item type from [Favorites] that was restored.
|
||||
*/
|
||||
override fun logSingleFavoritesItemRestored(favoritesId: Int) {
|
||||
if (Flags.enableLauncherBrMetrics()) {
|
||||
restoreEventLogger.logItemsRestored(favoritesIdToDataType(favoritesId), 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to log a failure to restore a single item from the Favorites table.
|
||||
*
|
||||
* @param favoritesId The id of the item type from [Favorites] that was not restored.
|
||||
* @param error error type for why the data was not restored.
|
||||
*/
|
||||
override fun logSingleFavoritesItemRestoreFailed(
|
||||
favoritesId: Int,
|
||||
@BackupRestoreError error: String?
|
||||
) {
|
||||
if (Flags.enableLauncherBrMetrics()) {
|
||||
restoreEventLogger.logItemsRestoreFailed(favoritesIdToDataType(favoritesId), 1, error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to log a failure to restore items from the Favorites table.
|
||||
*
|
||||
* @param favoritesId The id of the item type from [Favorites] that was not restored.
|
||||
* @param count number of items that failed to restore.
|
||||
* @param error error type for why the data was not restored.
|
||||
*/
|
||||
override fun logFavoritesItemsRestoreFailed(
|
||||
favoritesId: Int,
|
||||
count: Int,
|
||||
@BackupRestoreError error: String?
|
||||
) {
|
||||
if (Flags.enableLauncherBrMetrics()) {
|
||||
restoreEventLogger.logItemsRestoreFailed(
|
||||
favoritesIdToDataType(favoritesId),
|
||||
count,
|
||||
error
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the current [restoreEventLogger] to report its results to the [backupManager]. Use when
|
||||
* done restoring items for Launcher.
|
||||
*/
|
||||
override fun reportLauncherRestoreResults() {
|
||||
if (Flags.enableLauncherBrMetrics()) {
|
||||
backupManager.reportDelayedRestoreResult(restoreEventLogger)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to convert item types from [Favorites] to B&R data types for logging. Also to
|
||||
* avoid direct usage of @BackupRestoreDataType which is protected under @SystemApi.
|
||||
*/
|
||||
@BackupRestoreDataType
|
||||
private fun favoritesIdToDataType(favoritesId: Int): String =
|
||||
when (favoritesId) {
|
||||
Favorites.ITEM_TYPE_APPLICATION -> DATA_TYPE_APPLICATION
|
||||
Favorites.ITEM_TYPE_FOLDER -> DATA_TYPE_FOLDER
|
||||
Favorites.ITEM_TYPE_APPWIDGET -> DATA_TYPE_APPWIDGET
|
||||
Favorites.ITEM_TYPE_CUSTOM_APPWIDGET -> DATA_TYPE_CUSTOM_APPWIDGET
|
||||
Favorites.ITEM_TYPE_DEEP_SHORTCUT -> DATA_TYPE_DEEP_SHORTCUT
|
||||
Favorites.ITEM_TYPE_APP_PAIR -> DATA_TYPE_APP_PAIR
|
||||
else -> DATA_TYPE_LAUNCHER_ITEM
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import com.android.launcher3.statemanager.StatefulActivity;
|
||||
import com.android.launcher3.taskbar.TaskbarUIController;
|
||||
import com.android.launcher3.util.RunnableList;
|
||||
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
|
||||
import com.android.quickstep.util.ActiveGestureLog;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
@@ -78,6 +79,14 @@ public class OverviewCommandHelper {
|
||||
*/
|
||||
private int mTaskFocusIndexOverride = -1;
|
||||
|
||||
/**
|
||||
* Whether we should incoming toggle commands while a previous toggle command is still ongoing.
|
||||
* This serves as a rate-limiter to prevent overlapping animations that can clobber each other
|
||||
* and prevent clean-up callbacks from running. This thus prevents a recurring set of bugs with
|
||||
* janky recents animations and unresponsive home and overview buttons.
|
||||
*/
|
||||
private boolean mWaitForToggleCommandComplete = false;
|
||||
|
||||
public OverviewCommandHelper(TouchInteractionService service,
|
||||
OverviewComponentObserver observer,
|
||||
TaskAnimationManager taskAnimationManager) {
|
||||
@@ -160,15 +169,20 @@ public class OverviewCommandHelper {
|
||||
private boolean launchTask(RecentsView recents, @Nullable TaskView taskView, CommandInfo cmd) {
|
||||
RunnableList callbackList = null;
|
||||
if (taskView != null) {
|
||||
mWaitForToggleCommandComplete = true;
|
||||
taskView.setEndQuickswitchCuj(true);
|
||||
callbackList = taskView.launchTasks();
|
||||
}
|
||||
|
||||
if (callbackList != null) {
|
||||
callbackList.add(() -> scheduleNextTask(cmd));
|
||||
callbackList.add(() -> {
|
||||
scheduleNextTask(cmd);
|
||||
mWaitForToggleCommandComplete = false;
|
||||
});
|
||||
return false;
|
||||
} else {
|
||||
recents.startHome();
|
||||
mWaitForToggleCommandComplete = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -178,6 +192,9 @@ public class OverviewCommandHelper {
|
||||
* task is deferred until {@link #scheduleNextTask} is called
|
||||
*/
|
||||
private <T extends StatefulActivity<?>> boolean executeCommand(CommandInfo cmd) {
|
||||
if (mWaitForToggleCommandComplete && cmd.type == TYPE_TOGGLE) {
|
||||
return true;
|
||||
}
|
||||
BaseActivityInterface<?, T> activityInterface =
|
||||
mOverviewComponentObserver.getActivityInterface();
|
||||
RecentsView recents = activityInterface.getVisibleRecentsView();
|
||||
@@ -204,6 +221,7 @@ public class OverviewCommandHelper {
|
||||
return true;
|
||||
}
|
||||
if (cmd.type == TYPE_HOME) {
|
||||
ActiveGestureLog.INSTANCE.addLog("OverviewCommandHelper.executeCommand(TYPE_HOME)");
|
||||
mService.startActivity(mOverviewComponentObserver.getHomeIntent());
|
||||
return true;
|
||||
}
|
||||
@@ -359,6 +377,7 @@ public class OverviewCommandHelper {
|
||||
pw.println(" pendingCommandType=" + mPendingCommands.get(0).type);
|
||||
}
|
||||
pw.println(" mTaskFocusIndexOverride=" + mTaskFocusIndexOverride);
|
||||
pw.println(" mWaitForToggleCommandComplete=" + mWaitForToggleCommandComplete);
|
||||
}
|
||||
|
||||
private static class CommandInfo {
|
||||
|
||||
@@ -39,6 +39,7 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.util.SimpleBroadcastReceiver;
|
||||
import com.android.quickstep.util.ActiveGestureLog;
|
||||
import com.android.systemui.shared.system.PackageManagerWrapper;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
@@ -276,20 +277,24 @@ public final class OverviewComponentObserver {
|
||||
/**
|
||||
* Starts the intent for the current home activity.
|
||||
*/
|
||||
public static void startHomeIntentSafely(@NonNull Context context, @Nullable Bundle options) {
|
||||
public static void startHomeIntentSafely(@NonNull Context context, @Nullable Bundle options,
|
||||
@NonNull String reason) {
|
||||
RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(context);
|
||||
OverviewComponentObserver observer = new OverviewComponentObserver(context, deviceState);
|
||||
Intent intent = observer.getHomeIntent();
|
||||
observer.onDestroy();
|
||||
deviceState.destroy();
|
||||
startHomeIntentSafely(context, intent, options);
|
||||
startHomeIntentSafely(context, intent, options, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the intent for the current home activity.
|
||||
*/
|
||||
public static void startHomeIntentSafely(
|
||||
@NonNull Context context, @NonNull Intent homeIntent, @Nullable Bundle options) {
|
||||
@NonNull Context context, @NonNull Intent homeIntent, @Nullable Bundle options,
|
||||
@NonNull String reason) {
|
||||
ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
|
||||
"OverviewComponentObserver.startHomeIntent: ").append(reason));
|
||||
try {
|
||||
context.startActivity(homeIntent, options);
|
||||
} catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user