Snap for 12019843 from 1ec2aacc05 to 24Q4-release
Change-Id: I8a8760402e7605e29b416f7d6476a6d32bbf4003
This commit is contained in:
@@ -310,6 +310,13 @@ flag {
|
||||
}
|
||||
}
|
||||
|
||||
flag {
|
||||
name: "enable_container_return_animations"
|
||||
namespace: "launcher"
|
||||
description: "Enables the container return animation mirroring launches."
|
||||
bug: "341017746"
|
||||
}
|
||||
|
||||
flag {
|
||||
name: "floating_search_bar"
|
||||
namespace: "launcher"
|
||||
|
||||
@@ -238,5 +238,12 @@ public class LauncherAnimationRunner extends RemoteAnimationRunnerCompat {
|
||||
@Override
|
||||
@UiThread
|
||||
default void onAnimationCancelled() {}
|
||||
|
||||
/**
|
||||
* Returns whether this animation factory supports a tightly coupled return animation.
|
||||
*/
|
||||
default boolean supportsReturnTransition() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
|
||||
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
|
||||
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
|
||||
import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
|
||||
import static com.android.launcher3.Flags.enableContainerReturnAnimations;
|
||||
import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
|
||||
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
|
||||
@@ -68,6 +69,7 @@ import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITION
|
||||
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
|
||||
import static com.android.quickstep.util.AnimUtils.clampToDuration;
|
||||
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
|
||||
import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
|
||||
|
||||
@@ -181,6 +183,9 @@ import java.util.List;
|
||||
*/
|
||||
public class QuickstepTransitionManager implements OnDeviceProfileChangeListener {
|
||||
|
||||
private static final String TRANSITION_COOKIE_PREFIX =
|
||||
"com.android.launcher3.QuickstepTransitionManager_activityLaunch";
|
||||
|
||||
private static final boolean ENABLE_SHELL_STARTING_SURFACE =
|
||||
SystemProperties.getBoolean("persist.debug.shell_starting_surface", true);
|
||||
|
||||
@@ -333,17 +338,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
restartedListener.register(onEndCallback::executeAllAndDestroy);
|
||||
onEndCallback.add(restartedListener::unregister);
|
||||
|
||||
mAppLaunchRunner = new AppLaunchAnimationRunner(v, onEndCallback);
|
||||
ItemInfo tag = (ItemInfo) v.getTag();
|
||||
if (tag != null && tag.shouldUseBackgroundAnimation()) {
|
||||
ContainerAnimationRunner containerAnimationRunner = ContainerAnimationRunner.from(
|
||||
v, mLauncher, mStartingWindowListener, onEndCallback);
|
||||
if (containerAnimationRunner != null) {
|
||||
mAppLaunchRunner = containerAnimationRunner;
|
||||
}
|
||||
}
|
||||
RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(
|
||||
mHandler, mAppLaunchRunner, true /* startAtFrontOfQueue */);
|
||||
RemoteAnimationRunnerCompat runner = createAppLaunchRunner(v, onEndCallback);
|
||||
|
||||
// Note that this duration is a guess as we do not know if the animation will be a
|
||||
// recents launch or not for sure until we know the opening app targets.
|
||||
@@ -360,9 +355,94 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
IRemoteCallback endCallback = completeRunnableListCallback(onEndCallback);
|
||||
options.setOnAnimationAbortListener(endCallback);
|
||||
options.setOnAnimationFinishedListener(endCallback);
|
||||
|
||||
IBinder cookie = mAppLaunchRunner.supportsReturnTransition()
|
||||
? ((ContainerAnimationRunner) mAppLaunchRunner).getCookie() : null;
|
||||
addLaunchCookie(cookie, (ItemInfo) v.getTag(), options);
|
||||
|
||||
// Register the return animation so it can be triggered on back from the app to home.
|
||||
maybeRegisterAppReturnTransition(v);
|
||||
|
||||
return new ActivityOptionsWrapper(options, onEndCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the appropriate type of launch runner for the given view, builds it, and returns it.
|
||||
* {@link QuickstepTransitionManager#mAppLaunchRunner} is updated as a by-product of this
|
||||
* method.
|
||||
*/
|
||||
private RemoteAnimationRunnerCompat createAppLaunchRunner(View v, RunnableList onEndCallback) {
|
||||
ItemInfo tag = (ItemInfo) v.getTag();
|
||||
ContainerAnimationRunner containerRunner = null;
|
||||
if (tag != null && tag.shouldUseBackgroundAnimation()) {
|
||||
// The cookie should only override the default used by launcher if container return
|
||||
// animations are enabled.
|
||||
ActivityTransitionAnimator.TransitionCookie cookie =
|
||||
checkReturnAnimationsFlags()
|
||||
? new ActivityTransitionAnimator.TransitionCookie(
|
||||
TRANSITION_COOKIE_PREFIX + tag.id)
|
||||
: null;
|
||||
ContainerAnimationRunner launchAnimationRunner =
|
||||
ContainerAnimationRunner.fromView(
|
||||
v, cookie, true /* forLaunch */, mLauncher, mStartingWindowListener,
|
||||
onEndCallback);
|
||||
|
||||
if (launchAnimationRunner != null) {
|
||||
containerRunner = launchAnimationRunner;
|
||||
}
|
||||
}
|
||||
|
||||
mAppLaunchRunner = containerRunner != null
|
||||
? containerRunner : new AppLaunchAnimationRunner(v, onEndCallback);
|
||||
return new LauncherAnimationRunner(
|
||||
mHandler, mAppLaunchRunner, true /* startAtFrontOfQueue */);
|
||||
}
|
||||
|
||||
/**
|
||||
* If container return animations are enabled and the current launch runner is itself a
|
||||
* {@link ContainerAnimationRunner}, registers a matching return animation that de-registers
|
||||
* itself after it has run once or is made obsolete by the view going away.
|
||||
*/
|
||||
private void maybeRegisterAppReturnTransition(View v) {
|
||||
if (!checkReturnAnimationsFlags() || !mAppLaunchRunner.supportsReturnTransition()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ActivityTransitionAnimator.TransitionCookie cookie =
|
||||
((ContainerAnimationRunner) mAppLaunchRunner).getCookie();
|
||||
RunnableList onEndCallback = new RunnableList();
|
||||
ContainerAnimationRunner runner =
|
||||
ContainerAnimationRunner.fromView(
|
||||
v, cookie, false /* forLaunch */, mLauncher, mStartingWindowListener,
|
||||
onEndCallback);
|
||||
RemoteTransition transition =
|
||||
new RemoteTransition(
|
||||
new LauncherAnimationRunner(
|
||||
mHandler, runner, true /* startAtFrontOfQueue */
|
||||
).toRemoteTransition()
|
||||
);
|
||||
|
||||
SystemUiProxy.INSTANCE.get(mLauncher).registerRemoteTransition(
|
||||
transition, ContainerAnimationRunner.buildBackToHomeFilter(cookie, mLauncher));
|
||||
ContainerAnimationRunner.setUpRemoteAnimationCleanup(
|
||||
v, transition, onEndCallback, mLauncher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new launch cookie for the activity launch if supported.
|
||||
* Prioritizes the explicitly provided cookie, falling back on extracting one from the given
|
||||
* {@link ItemInfo} if necessary.
|
||||
*/
|
||||
private void addLaunchCookie(IBinder cookie, ItemInfo info, ActivityOptions options) {
|
||||
if (cookie == null) {
|
||||
cookie = mLauncher.getLaunchCookie(info);
|
||||
}
|
||||
|
||||
if (cookie != null) {
|
||||
options.setLaunchCookie(cookie);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the launch is a recents app transition and we should do a launch animation
|
||||
* from the recents view. Note that if the remote animation targets are not provided, this
|
||||
@@ -1728,6 +1808,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean checkReturnAnimationsFlags() {
|
||||
return enableContainerReturnAnimations() && returnAnimationFrameworkLibrary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote animation runner for animation from the app to Launcher, including recents.
|
||||
*/
|
||||
@@ -1844,38 +1928,45 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
/** The delegate runner that handles the actual animation. */
|
||||
private final RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> mDelegate;
|
||||
|
||||
@Nullable
|
||||
private final ActivityTransitionAnimator.TransitionCookie mCookie;
|
||||
|
||||
private ContainerAnimationRunner(
|
||||
RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> delegate) {
|
||||
RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> delegate,
|
||||
ActivityTransitionAnimator.TransitionCookie cookie) {
|
||||
mDelegate = delegate;
|
||||
mCookie = cookie;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ContainerAnimationRunner from(View v, Launcher launcher,
|
||||
StartingWindowListener startingWindowListener, RunnableList onEndCallback) {
|
||||
View viewToUse = findLaunchableViewWithBackground(v);
|
||||
if (viewToUse == null) {
|
||||
return null;
|
||||
ActivityTransitionAnimator.TransitionCookie getCookie() {
|
||||
return mCookie;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static ContainerAnimationRunner fromView(
|
||||
View v,
|
||||
ActivityTransitionAnimator.TransitionCookie cookie,
|
||||
boolean forLaunch,
|
||||
Launcher launcher,
|
||||
StartingWindowListener startingWindowListener,
|
||||
RunnableList onEndCallback) {
|
||||
if (!forLaunch && !checkReturnAnimationsFlags()) {
|
||||
throw new IllegalStateException(
|
||||
"forLaunch cannot be false when the enableContainerReturnAnimations or "
|
||||
+ "returnAnimationFrameworkLibrary flag is disabled");
|
||||
}
|
||||
|
||||
// The CUJ is logged by the click handler, so we don't log it inside the animation
|
||||
// library.
|
||||
ActivityTransitionAnimator.Controller controllerDelegate =
|
||||
ActivityTransitionAnimator.Controller.fromView(viewToUse, null /* cujType */);
|
||||
|
||||
if (controllerDelegate == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// This wrapper allows us to override the default value, telling the controller that the
|
||||
// current window is below the animating window.
|
||||
// First the controller is created. This is used by the runner to animate the
|
||||
// origin/target view.
|
||||
ActivityTransitionAnimator.Controller controller =
|
||||
new DelegateTransitionAnimatorController(controllerDelegate) {
|
||||
@Override
|
||||
public boolean isBelowAnimatingWindow() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
buildController(v, cookie, forLaunch);
|
||||
if (controller == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The callback is used to make sure that we use the right color to fade between view
|
||||
// and the window.
|
||||
ActivityTransitionAnimator.Callback callback = task -> {
|
||||
final int backgroundColor =
|
||||
startingWindowListener.mBackgroundColor == Color.TRANSPARENT
|
||||
@@ -1894,7 +1985,52 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
|
||||
return new ContainerAnimationRunner(
|
||||
new ActivityTransitionAnimator.AnimationDelegate(
|
||||
MAIN_EXECUTOR, controller, callback, listener));
|
||||
MAIN_EXECUTOR, controller, callback, listener),
|
||||
cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link ActivityTransitionAnimator.Controller} that can be used by a
|
||||
* {@link ContainerAnimationRunner} to animate a view into an opening window or from a
|
||||
* closing one.
|
||||
*/
|
||||
@Nullable
|
||||
private static ActivityTransitionAnimator.Controller buildController(
|
||||
View v, ActivityTransitionAnimator.TransitionCookie cookie, boolean isLaunching) {
|
||||
View viewToUse = findLaunchableViewWithBackground(v);
|
||||
if (viewToUse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The CUJ is logged by the click handler, so we don't log it inside the animation
|
||||
// library. TODO: figure out return CUJ.
|
||||
ActivityTransitionAnimator.Controller controllerDelegate =
|
||||
ActivityTransitionAnimator.Controller.fromView(viewToUse, null /* cujType */);
|
||||
|
||||
if (controllerDelegate == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// This wrapper allows us to override the default value, telling the controller that the
|
||||
// current window is below the animating window as well as information about the return
|
||||
// animation.
|
||||
return new DelegateTransitionAnimatorController(controllerDelegate) {
|
||||
@Override
|
||||
public boolean isLaunching() {
|
||||
return isLaunching;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBelowAnimatingWindow() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ActivityTransitionAnimator.TransitionCookie getTransitionCookie() {
|
||||
return cookie;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1916,6 +2052,67 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
return (T) current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the filter used by WM Shell to match app closing transitions (only back, no home
|
||||
* button/gesture) to the given launch cookie.
|
||||
*/
|
||||
static TransitionFilter buildBackToHomeFilter(
|
||||
ActivityTransitionAnimator.TransitionCookie cookie, Launcher launcher) {
|
||||
// Closing activity must include the cookie in its list of launch cookies.
|
||||
TransitionFilter.Requirement appRequirement = new TransitionFilter.Requirement();
|
||||
appRequirement.mActivityType = ACTIVITY_TYPE_STANDARD;
|
||||
appRequirement.mLaunchCookie = cookie;
|
||||
appRequirement.mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
|
||||
// Opening activity must be Launcher.
|
||||
TransitionFilter.Requirement launcherRequirement = new TransitionFilter.Requirement();
|
||||
launcherRequirement.mActivityType = ACTIVITY_TYPE_HOME;
|
||||
launcherRequirement.mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
|
||||
launcherRequirement.mTopActivity = launcher.getComponentName();
|
||||
// Transition types CLOSE and TO_BACK match the back button/gesture but not the home
|
||||
// button/gesture.
|
||||
TransitionFilter filter = new TransitionFilter();
|
||||
filter.mTypeSet = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
|
||||
filter.mRequirements =
|
||||
new TransitionFilter.Requirement[]{appRequirement, launcherRequirement};
|
||||
return filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates various conditions to ensure that the given transition is cleaned up correctly
|
||||
* when necessary:
|
||||
* - if the transition has run, it is the callback that unregisters it;
|
||||
* - if the associated view is detached before the transition has had an opportunity to run,
|
||||
* a {@link View.OnAttachStateChangeListener} allows us to do the same (and removes
|
||||
* itself).
|
||||
*/
|
||||
static void setUpRemoteAnimationCleanup(
|
||||
View v, RemoteTransition transition, RunnableList callback, Launcher launcher) {
|
||||
View.OnAttachStateChangeListener listener = new View.OnAttachStateChangeListener() {
|
||||
@Override
|
||||
public void onViewAttachedToWindow(@NonNull View v) {}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(@NonNull View v) {
|
||||
SystemUiProxy.INSTANCE.get(launcher)
|
||||
.unregisterRemoteTransition(transition);
|
||||
v.removeOnAttachStateChangeListener(this);
|
||||
}
|
||||
};
|
||||
|
||||
// Remove the animation as soon as it has run once.
|
||||
callback.add(() -> {
|
||||
SystemUiProxy.INSTANCE.get(launcher).unregisterRemoteTransition(transition);
|
||||
if (v != null) {
|
||||
v.removeOnAttachStateChangeListener(listener);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove the animation when the view is detached from the hierarchy.
|
||||
// This is so that if back is not invoked (e.g. if we go back home through the home
|
||||
// gesture) we don't have obsolete transitions staying registered.
|
||||
v.addOnAttachStateChangeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(int transit, RemoteAnimationTarget[] appTargets,
|
||||
RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets,
|
||||
@@ -1928,6 +2125,11 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
|
||||
public void onAnimationCancelled() {
|
||||
mDelegate.onAnimationCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsReturnTransition() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -120,10 +120,6 @@ public class WidgetPickerActivity extends BaseActivity {
|
||||
WindowInsetsController wc = mDragLayer.getWindowInsetsController();
|
||||
wc.hide(navigationBars() + statusBars());
|
||||
|
||||
BaseWidgetSheet widgetSheet = WidgetsFullSheet.show(this, true);
|
||||
widgetSheet.disableNavBarScrim(true);
|
||||
widgetSheet.addOnCloseListener(this::finish);
|
||||
|
||||
parseIntentExtras();
|
||||
refreshAndBindWidgets();
|
||||
}
|
||||
@@ -224,9 +220,10 @@ public class WidgetPickerActivity extends BaseActivity {
|
||||
};
|
||||
}
|
||||
|
||||
/** Updates the model with widgets and provides them after applying the provided filter. */
|
||||
/** Updates the model with widgets, applies filters and launches the widgets sheet once
|
||||
* widgets are available */
|
||||
private void refreshAndBindWidgets() {
|
||||
MODEL_EXECUTOR.execute(() -> {
|
||||
MODEL_EXECUTOR.getHandler().postDelayed(() -> {
|
||||
LauncherAppState app = LauncherAppState.getInstance(this);
|
||||
mModel.update(app, null);
|
||||
final List<WidgetsListBaseEntry> allWidgets =
|
||||
@@ -240,6 +237,9 @@ public class WidgetPickerActivity extends BaseActivity {
|
||||
}
|
||||
);
|
||||
bindWidgets(allWidgets);
|
||||
// Open sheet once widgets are available, so that it doesn't interrupt the open
|
||||
// animation.
|
||||
openWidgetsSheet();
|
||||
if (mUiSurface != null) {
|
||||
Map<ComponentKey, WidgetItem> allWidgetItems = allWidgets.stream()
|
||||
.filter(entry -> entry instanceof WidgetsListContentEntry)
|
||||
@@ -253,15 +253,26 @@ public class WidgetPickerActivity extends BaseActivity {
|
||||
mUiSurface, allWidgetItems);
|
||||
mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets);
|
||||
}
|
||||
});
|
||||
}, mDeviceProfile.bottomSheetOpenDuration);
|
||||
}
|
||||
|
||||
private void bindWidgets(List<WidgetsListBaseEntry> widgets) {
|
||||
MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets));
|
||||
}
|
||||
|
||||
private void openWidgetsSheet() {
|
||||
MAIN_EXECUTOR.execute(() -> {
|
||||
BaseWidgetSheet widgetSheet = WidgetsFullSheet.show(this, true);
|
||||
widgetSheet.disableNavBarScrim(true);
|
||||
widgetSheet.addOnCloseListener(this::finish);
|
||||
});
|
||||
}
|
||||
|
||||
private void bindRecommendedWidgets(List<ItemInfo> recommendedWidgets) {
|
||||
MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setRecommendedWidgets(recommendedWidgets));
|
||||
// Bind recommendations once picker has finished open animation.
|
||||
MAIN_EXECUTOR.getHandler().postDelayed(
|
||||
() -> mPopupDataProvider.setRecommendedWidgets(recommendedWidgets),
|
||||
mDeviceProfile.bottomSheetOpenDuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -65,6 +65,7 @@ public class WidgetPredictionsRequester {
|
||||
private final Context mContext;
|
||||
@NonNull
|
||||
private final String mUiSurface;
|
||||
private boolean mPredictionsAvailable;
|
||||
@NonNull
|
||||
private final Map<ComponentKey, WidgetItem> mAllWidgets;
|
||||
|
||||
@@ -76,8 +77,8 @@ public class WidgetPredictionsRequester {
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests predictions from the app predictions manager and registers the provided callback to
|
||||
* receive updates when predictions are available.
|
||||
* Requests one time predictions from the app predictions manager and invokes provided callback
|
||||
* once predictions are available.
|
||||
*
|
||||
* @param existingWidgets widgets that are currently added to the surface;
|
||||
* @param callback consumer of prediction results to be called when predictions are
|
||||
@@ -159,10 +160,14 @@ public class WidgetPredictionsRequester {
|
||||
@WorkerThread
|
||||
private void bindPredictions(List<AppTarget> targets, Predicate<WidgetItem> filter,
|
||||
Consumer<List<ItemInfo>> callback) {
|
||||
List<WidgetItem> filteredPredictions = filterPredictions(targets, mAllWidgets, filter);
|
||||
List<ItemInfo> mappedPredictions = mapWidgetItemsToItemInfo(filteredPredictions);
|
||||
if (!mPredictionsAvailable) {
|
||||
mPredictionsAvailable = true;
|
||||
List<WidgetItem> filteredPredictions = filterPredictions(targets, mAllWidgets, filter);
|
||||
List<ItemInfo> mappedPredictions = mapWidgetItemsToItemInfo(filteredPredictions);
|
||||
|
||||
MAIN_EXECUTOR.execute(() -> callback.accept(mappedPredictions));
|
||||
MAIN_EXECUTOR.execute(() -> callback.accept(mappedPredictions));
|
||||
MODEL_EXECUTOR.execute(this::clear);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,5 +219,6 @@ public class WidgetPredictionsRequester {
|
||||
mAppPredictor.destroy();
|
||||
mAppPredictor = null;
|
||||
}
|
||||
mPredictionsAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,8 +269,8 @@ public class TaskbarUIController {
|
||||
foundTaskView,
|
||||
foundTask,
|
||||
taskContainer.getIconView().getDrawable(),
|
||||
taskContainer.getThumbnailViewDeprecated(),
|
||||
taskContainer.getThumbnailViewDeprecated().getThumbnail(),
|
||||
taskContainer.getSnapshotView(),
|
||||
taskContainer.getThumbnail(),
|
||||
null /* intent */,
|
||||
null /* user */,
|
||||
info);
|
||||
|
||||
@@ -1200,7 +1200,6 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer
|
||||
: Display.DEFAULT_DISPLAY);
|
||||
activityOptions.options.setPendingIntentBackgroundActivityStartMode(
|
||||
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
|
||||
addLaunchCookie(item, activityOptions.options);
|
||||
return activityOptions;
|
||||
}
|
||||
|
||||
@@ -1224,19 +1223,6 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer
|
||||
mSplitWithKeyboardShortcutController.enterStageSplit(leftOrTop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new launch cookie for the activity launch if supported.
|
||||
*
|
||||
* @param info the item info for the launch
|
||||
* @param opts the options to set the launchCookie on.
|
||||
*/
|
||||
public void addLaunchCookie(ItemInfo info, ActivityOptions opts) {
|
||||
IBinder launchCookie = getLaunchCookie(info);
|
||||
if (launchCookie != null) {
|
||||
opts.setLaunchCookie(launchCookie);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new launch cookie for the activity launch if supported.
|
||||
*
|
||||
|
||||
+1
-1
@@ -252,7 +252,7 @@ public abstract class TaskViewTouchController<CONTAINER extends Context & Recent
|
||||
mTaskBeingDragged, maxDuration, currentInterpolator);
|
||||
|
||||
// Since the thumbnail is what is filling the screen, based the end displacement on it.
|
||||
View thumbnailView = mTaskBeingDragged.getFirstThumbnailViewDeprecated();
|
||||
View thumbnailView = mTaskBeingDragged.getFirstSnapshotView();
|
||||
mTempCords[1] = orientationHandler.getSecondaryDimension(thumbnailView);
|
||||
dl.getDescendantCoordRelativeToSelf(thumbnailView, mTempCords);
|
||||
mEndDisplacement = secondaryLayerDimension - mTempCords[1];
|
||||
|
||||
@@ -44,6 +44,7 @@ import com.android.wm.shell.util.GroupedRecentTaskInfo;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
@@ -324,7 +325,9 @@ public class RecentTasksList {
|
||||
// leftover TYPE_FREEFORM tasks created when flag was on should be ignored.
|
||||
if (enableDesktopWindowingMode()) {
|
||||
GroupTask desktopTask = createDesktopTask(rawTask);
|
||||
allTasks.add(desktopTask);
|
||||
if (desktopTask != null) {
|
||||
allTasks.add(desktopTask);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -368,8 +371,13 @@ public class RecentTasksList {
|
||||
return allTasks;
|
||||
}
|
||||
|
||||
private DesktopTask createDesktopTask(GroupedRecentTaskInfo recentTaskInfo) {
|
||||
private @Nullable DesktopTask createDesktopTask(GroupedRecentTaskInfo recentTaskInfo) {
|
||||
ArrayList<Task> tasks = new ArrayList<>(recentTaskInfo.getTaskInfoList().size());
|
||||
int[] minimizedTaskIds = recentTaskInfo.getMinimizedTaskIds();
|
||||
if (minimizedTaskIds.length == recentTaskInfo.getTaskInfoList().size()) {
|
||||
// All Tasks are minimized -> don't create a DesktopTask
|
||||
return null;
|
||||
}
|
||||
for (ActivityManager.RecentTaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) {
|
||||
Task.TaskKey key = new Task.TaskKey(taskInfo);
|
||||
Task task = Task.from(key, taskInfo, false);
|
||||
@@ -377,6 +385,8 @@ public class RecentTasksList {
|
||||
task.positionInParent = taskInfo.positionInParent;
|
||||
task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds();
|
||||
task.isVisible = taskInfo.isVisible;
|
||||
task.isMinimized =
|
||||
Arrays.stream(minimizedTaskIds).anyMatch(taskId -> taskId == taskInfo.taskId);
|
||||
tasks.add(task);
|
||||
}
|
||||
return new DesktopTask(tasks);
|
||||
|
||||
@@ -44,7 +44,6 @@ import com.android.quickstep.views.GroupedTaskView;
|
||||
import com.android.quickstep.views.OverviewActionsView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.RecentsViewContainer;
|
||||
import com.android.quickstep.views.TaskThumbnailViewDeprecated;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.quickstep.views.TaskView.TaskContainer;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
@@ -134,20 +133,20 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
|
||||
mApplicationContext = taskContainer.getTaskView().getContext().getApplicationContext();
|
||||
mTaskContainer = taskContainer;
|
||||
mImageApi = new ImageActionsApi(
|
||||
mApplicationContext, mTaskContainer.getThumbnailViewDeprecated()::getThumbnail);
|
||||
mApplicationContext, mTaskContainer::getThumbnail);
|
||||
}
|
||||
|
||||
protected T getActionsView() {
|
||||
if (mActionsView == null) {
|
||||
mActionsView = BaseActivity.fromContext(
|
||||
mTaskContainer.getThumbnailViewDeprecated().getContext()).findViewById(
|
||||
mTaskContainer.getTaskView().getContext()).findViewById(
|
||||
R.id.overview_actions_view);
|
||||
}
|
||||
return mActionsView;
|
||||
}
|
||||
|
||||
public TaskThumbnailViewDeprecated getThumbnailView() {
|
||||
return mTaskContainer.getThumbnailViewDeprecated();
|
||||
public TaskView getTaskView() {
|
||||
return mTaskContainer.getTaskView();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,8 +158,7 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
|
||||
|
||||
if (thumbnail != null) {
|
||||
getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
|
||||
boolean isAllowedByPolicy =
|
||||
mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot();
|
||||
boolean isAllowedByPolicy = mTaskContainer.isRealSnapshot();
|
||||
getActionsView().setCallbacks(new OverlayUICallbacksImpl(isAllowedByPolicy, task));
|
||||
}
|
||||
}
|
||||
@@ -172,7 +170,7 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
|
||||
*/
|
||||
public void endLiveTileMode(@NonNull Runnable callback) {
|
||||
RecentsView recentsView =
|
||||
mTaskContainer.getThumbnailViewDeprecated().getTaskView().getRecentsView();
|
||||
mTaskContainer.getTaskView().getRecentsView();
|
||||
// Task has already been dismissed
|
||||
if (recentsView == null) return;
|
||||
recentsView.switchToScreenshot(
|
||||
@@ -185,8 +183,8 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
protected void saveScreenshot(Task task) {
|
||||
if (mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot()) {
|
||||
mImageApi.saveScreenshot(mTaskContainer.getThumbnailViewDeprecated().getThumbnail(),
|
||||
if (mTaskContainer.isRealSnapshot()) {
|
||||
mImageApi.saveScreenshot(mTaskContainer.getThumbnail(),
|
||||
getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key);
|
||||
} else {
|
||||
showBlockedByPolicyMessage();
|
||||
@@ -194,17 +192,14 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
|
||||
}
|
||||
|
||||
protected void enterSplitSelect() {
|
||||
RecentsView overviewPanel =
|
||||
mTaskContainer.getThumbnailViewDeprecated().getTaskView().getRecentsView();
|
||||
RecentsView overviewPanel = mTaskContainer.getTaskView().getRecentsView();
|
||||
// Task has already been dismissed
|
||||
if (overviewPanel == null) return;
|
||||
overviewPanel.initiateSplitSelect(
|
||||
mTaskContainer.getThumbnailViewDeprecated().getTaskView());
|
||||
overviewPanel.initiateSplitSelect(mTaskContainer.getTaskView());
|
||||
}
|
||||
|
||||
protected void saveAppPair() {
|
||||
GroupedTaskView taskView =
|
||||
(GroupedTaskView) mTaskContainer.getThumbnailViewDeprecated().getTaskView();
|
||||
GroupedTaskView taskView = (GroupedTaskView) mTaskContainer.getTaskView();
|
||||
taskView.getRecentsView().getSplitSelectController().getAppPairsController()
|
||||
.saveAppPair(taskView);
|
||||
}
|
||||
@@ -250,11 +245,11 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
|
||||
*/
|
||||
public Rect getTaskSnapshotBounds() {
|
||||
int[] location = new int[2];
|
||||
mTaskContainer.getThumbnailViewDeprecated().getLocationOnScreen(location);
|
||||
mTaskContainer.getSnapshotView().getLocationOnScreen(location);
|
||||
|
||||
return new Rect(location[0], location[1],
|
||||
mTaskContainer.getThumbnailViewDeprecated().getWidth() + location[0],
|
||||
mTaskContainer.getThumbnailViewDeprecated().getHeight() + location[1]);
|
||||
mTaskContainer.getSnapshotView().getWidth() + location[0],
|
||||
mTaskContainer.getSnapshotView().getHeight() + location[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,7 +259,7 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.Q)
|
||||
public Insets getTaskSnapshotInsets() {
|
||||
return mTaskContainer.getThumbnailViewDeprecated().getScaledInsets();
|
||||
return mTaskContainer.getScaledInsets();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,14 +270,14 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
|
||||
|
||||
protected void showBlockedByPolicyMessage() {
|
||||
ActivityContext activityContext = ActivityContext.lookupContext(
|
||||
mTaskContainer.getThumbnailViewDeprecated().getContext());
|
||||
mTaskContainer.getTaskView().getContext());
|
||||
String message = activityContext.getStringCache() != null
|
||||
? activityContext.getStringCache().disabledByAdminMessage
|
||||
: mTaskContainer.getThumbnailViewDeprecated().getContext().getString(
|
||||
: mTaskContainer.getTaskView().getContext().getString(
|
||||
R.string.blocked_by_policy);
|
||||
|
||||
Snackbar.show(BaseActivity.fromContext(
|
||||
mTaskContainer.getThumbnailViewDeprecated().getContext()), message, null);
|
||||
mTaskContainer.getTaskView().getContext()), message, null);
|
||||
}
|
||||
|
||||
/** Called when the snapshot has updated its full screen drawing parameters. */
|
||||
@@ -304,8 +299,7 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
saveScreenshot(
|
||||
mTaskContainer.getThumbnailViewDeprecated().getTaskView().getFirstTask());
|
||||
saveScreenshot(mTaskContainer.getTaskView().getFirstTask());
|
||||
dismissTaskMenuView();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
|
||||
import static android.view.Surface.ROTATION_0;
|
||||
|
||||
import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
|
||||
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
|
||||
@@ -55,7 +56,6 @@ import com.android.quickstep.util.RecentsOrientedState;
|
||||
import com.android.quickstep.views.GroupedTaskView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.RecentsViewContainer;
|
||||
import com.android.quickstep.views.TaskThumbnailViewDeprecated;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.quickstep.views.TaskView.TaskContainer;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
@@ -174,7 +174,7 @@ public interface TaskShortcutFactory {
|
||||
private Handler mHandler;
|
||||
|
||||
private final RecentsView mRecentsView;
|
||||
private final TaskThumbnailViewDeprecated mThumbnailView;
|
||||
private final TaskContainer mTaskContainer;
|
||||
private final TaskView mTaskView;
|
||||
private final LauncherEvent mLauncherEvent;
|
||||
|
||||
@@ -186,7 +186,7 @@ public interface TaskShortcutFactory {
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
mTaskView = taskContainer.getTaskView();
|
||||
mRecentsView = container.getOverviewPanel();
|
||||
mThumbnailView = taskContainer.getThumbnailViewDeprecated();
|
||||
mTaskContainer = taskContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -220,20 +220,25 @@ public interface TaskShortcutFactory {
|
||||
};
|
||||
|
||||
final int[] position = new int[2];
|
||||
mThumbnailView.getLocationOnScreen(position);
|
||||
final int width = (int) (mThumbnailView.getWidth() * mTaskView.getScaleX());
|
||||
final int height = (int) (mThumbnailView.getHeight() * mTaskView.getScaleY());
|
||||
View snapShotView = mTaskContainer.getSnapshotView();
|
||||
snapShotView.getLocationOnScreen(position);
|
||||
final int width = (int) (snapShotView.getWidth() * mTaskView.getScaleX());
|
||||
final int height = (int) (snapShotView.getHeight() * mTaskView.getScaleY());
|
||||
final Rect taskBounds = new Rect(position[0], position[1],
|
||||
position[0] + width, position[1] + height);
|
||||
|
||||
// Take the thumbnail of the task without a scrim and apply it back after
|
||||
float alpha = mThumbnailView.getDimAlpha();
|
||||
// TODO(b/348643341) add ability to get override the scrim for this Bitmap retrieval
|
||||
mThumbnailView.setDimAlpha(0);
|
||||
float alpha = 0f;
|
||||
if (!enableRefactorTaskThumbnail()) {
|
||||
alpha = mTaskContainer.getThumbnailViewDeprecated().getDimAlpha();
|
||||
mTaskContainer.getThumbnailViewDeprecated().setDimAlpha(0);
|
||||
}
|
||||
Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
|
||||
taskBounds.width(), taskBounds.height(), mThumbnailView, 1f,
|
||||
Color.BLACK);
|
||||
mThumbnailView.setDimAlpha(alpha);
|
||||
taskBounds.width(), taskBounds.height(), snapShotView, 1f, Color.BLACK);
|
||||
if (!enableRefactorTaskThumbnail()) {
|
||||
mTaskContainer.getThumbnailViewDeprecated().setDimAlpha(alpha);
|
||||
}
|
||||
|
||||
AppTransitionAnimationSpecsFuture future =
|
||||
new AppTransitionAnimationSpecsFuture(mHandler) {
|
||||
|
||||
@@ -80,7 +80,6 @@ import com.android.quickstep.util.TransformParams;
|
||||
import com.android.quickstep.views.DesktopTaskView;
|
||||
import com.android.quickstep.views.GroupedTaskView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskThumbnailViewDeprecated;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.systemui.animation.RemoteAnimationTargetCompat;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
@@ -334,7 +333,7 @@ public final class TaskViewUtils {
|
||||
// During animation we apply transformation on the thumbnailView (and not the rootView)
|
||||
// to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is:
|
||||
// Mt K(0)` K(t) Mt`
|
||||
TaskThumbnailViewDeprecated[] thumbnails = v.getThumbnailViews();
|
||||
View[] thumbnails = v.getSnapshotViews();
|
||||
|
||||
// In case simulator copies and thumbnail size do no match, ensure we get the lesser.
|
||||
// This ensures we do not create arrays with empty elements or attempt to references
|
||||
@@ -344,7 +343,7 @@ public final class TaskViewUtils {
|
||||
Matrix[] mt = new Matrix[matrixSize];
|
||||
Matrix[] mti = new Matrix[matrixSize];
|
||||
for (int i = 0; i < matrixSize; i++) {
|
||||
TaskThumbnailViewDeprecated ttv = thumbnails[i];
|
||||
View ttv = thumbnails[i];
|
||||
RectF localBounds = new RectF(0, 0, ttv.getWidth(), ttv.getHeight());
|
||||
float[] tvBoundsMapped = new float[]{0, 0, ttv.getWidth(), ttv.getHeight()};
|
||||
getDescendantCoordRelativeToAncestor(ttv, ttv.getRootView(), tvBoundsMapped, false);
|
||||
@@ -391,7 +390,7 @@ public final class TaskViewUtils {
|
||||
out.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
for (TaskThumbnailViewDeprecated ttv : thumbnails) {
|
||||
for (View ttv : thumbnails) {
|
||||
ttv.setAnimationMatrix(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import androidx.annotation.VisibleForTesting
|
||||
import com.android.app.animation.Interpolators
|
||||
import com.android.launcher3.DeviceProfile
|
||||
import com.android.launcher3.Flags.enableOverviewIconMenu
|
||||
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
|
||||
import com.android.launcher3.InsettableFrameLayout
|
||||
import com.android.launcher3.QuickstepTransitionManager
|
||||
import com.android.launcher3.R
|
||||
@@ -118,8 +119,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
|
||||
if (container.task.getKey().getId() == splitSelectStateController.initialTaskId) {
|
||||
val drawable = getDrawable(container.iconView, splitSelectSource)
|
||||
return SplitAnimInitProps(
|
||||
container.thumbnailViewDeprecated,
|
||||
container.thumbnailViewDeprecated.thumbnail,
|
||||
container.snapshotView,
|
||||
container.thumbnail,
|
||||
drawable!!,
|
||||
fadeWithThumbnail = true,
|
||||
isStagedTask = true,
|
||||
@@ -137,8 +138,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
|
||||
taskView.taskContainers.first().let {
|
||||
val drawable = getDrawable(it.iconView, splitSelectSource)
|
||||
return SplitAnimInitProps(
|
||||
it.thumbnailViewDeprecated,
|
||||
it.thumbnailViewDeprecated.thumbnail,
|
||||
it.snapshotView,
|
||||
it.thumbnail,
|
||||
drawable!!,
|
||||
fadeWithThumbnail = true,
|
||||
isStagedTask = true,
|
||||
@@ -165,27 +166,37 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
|
||||
/**
|
||||
* When selecting first app from split pair, second app's thumbnail remains. This animates the
|
||||
* second thumbnail by expanding it to take up the full taskViewWidth/Height and overlaying it
|
||||
* with [TaskThumbnailViewDeprecated]'s splashView. Adds animations to the provided builder.
|
||||
* Note: The app that **was not** selected as the first split app should be the container that's
|
||||
* passed through.
|
||||
* with [TaskContainer]'s splashView. Adds animations to the provided builder. Note: The app
|
||||
* that **was not** selected as the first split app should be the container that's passed
|
||||
* through.
|
||||
*
|
||||
* @param builder Adds animation to this
|
||||
* @param taskIdAttributeContainer container of the app that **was not** selected
|
||||
* @param taskContainer container of the app that **was not** selected
|
||||
* @param isPrimaryTaskSplitting if true, task that was split would be top/left in the pair
|
||||
* (opposite of that representing [taskIdAttributeContainer])
|
||||
* (opposite of that representing [taskContainer])
|
||||
*/
|
||||
fun addInitialSplitFromPair(
|
||||
taskIdAttributeContainer: TaskContainer,
|
||||
taskContainer: TaskContainer,
|
||||
builder: PendingAnimation,
|
||||
deviceProfile: DeviceProfile,
|
||||
taskViewWidth: Int,
|
||||
taskViewHeight: Int,
|
||||
isPrimaryTaskSplitting: Boolean
|
||||
) {
|
||||
val thumbnail = taskIdAttributeContainer.thumbnailViewDeprecated
|
||||
val iconView: View = taskIdAttributeContainer.iconView.asView()
|
||||
builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailViewDeprecated.SPLASH_ALPHA, 1f))
|
||||
thumbnail.setShowSplashForSplitSelection(true)
|
||||
val snapshot = taskContainer.snapshotView
|
||||
val iconView: View = taskContainer.iconView.asView()
|
||||
// TODO(334826842): Switch to splash state in TaskThumbnailView
|
||||
if (!enableRefactorTaskThumbnail()) {
|
||||
val thumbnailViewDeprecated = taskContainer.thumbnailViewDeprecated
|
||||
builder.add(
|
||||
ObjectAnimator.ofFloat(
|
||||
thumbnailViewDeprecated,
|
||||
TaskThumbnailViewDeprecated.SPLASH_ALPHA,
|
||||
1f
|
||||
)
|
||||
)
|
||||
thumbnailViewDeprecated.setShowSplashForSplitSelection(true)
|
||||
}
|
||||
// With the new `IconAppChipView`, we always want to keep the chip pinned to the
|
||||
// top left of the task / thumbnail.
|
||||
if (enableOverviewIconMenu()) {
|
||||
@@ -202,14 +213,10 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
|
||||
}
|
||||
if (deviceProfile.isLeftRightSplit) {
|
||||
// Center view first so scaling happens uniformly, alternatively we can move pivotX to 0
|
||||
val centerThumbnailTranslationX: Float = (taskViewWidth - thumbnail.width) / 2f
|
||||
val finalScaleX: Float = taskViewWidth.toFloat() / thumbnail.width
|
||||
val centerThumbnailTranslationX: Float = (taskViewWidth - snapshot.width) / 2f
|
||||
val finalScaleX: Float = taskViewWidth.toFloat() / snapshot.width
|
||||
builder.add(
|
||||
ObjectAnimator.ofFloat(
|
||||
thumbnail,
|
||||
TaskThumbnailViewDeprecated.SPLIT_SELECT_TRANSLATE_X,
|
||||
centerThumbnailTranslationX
|
||||
)
|
||||
ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_X, centerThumbnailTranslationX)
|
||||
)
|
||||
if (!enableOverviewIconMenu()) {
|
||||
// icons are anchored from Gravity.END, so need to use negative translation
|
||||
@@ -218,21 +225,15 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
|
||||
ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, -centerIconTranslationX)
|
||||
)
|
||||
}
|
||||
builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_X, finalScaleX))
|
||||
builder.add(ObjectAnimator.ofFloat(snapshot, View.SCALE_X, finalScaleX))
|
||||
|
||||
// Reset other dimensions
|
||||
// TODO(b/271468547), can't set Y translate to 0, need to account for top space
|
||||
thumbnail.scaleY = 1f
|
||||
snapshot.scaleY = 1f
|
||||
val translateYResetVal: Float =
|
||||
if (!isPrimaryTaskSplitting) 0f
|
||||
else deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
|
||||
builder.add(
|
||||
ObjectAnimator.ofFloat(
|
||||
thumbnail,
|
||||
TaskThumbnailViewDeprecated.SPLIT_SELECT_TRANSLATE_Y,
|
||||
translateYResetVal
|
||||
)
|
||||
)
|
||||
builder.add(ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_Y, translateYResetVal))
|
||||
} else {
|
||||
val thumbnailSize = taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
|
||||
// Center view first so scaling happens uniformly, alternatively we can move pivotY to 0
|
||||
@@ -247,36 +248,26 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
|
||||
// thumbnail needs to take that into account. We should migrate to only using
|
||||
// translations otherwise this asymmetry causes problems..
|
||||
if (isPrimaryTaskSplitting) {
|
||||
centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
|
||||
centerThumbnailTranslationY = (thumbnailSize - snapshot.height) / 2f
|
||||
centerThumbnailTranslationY +=
|
||||
deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
|
||||
} else {
|
||||
centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
|
||||
centerThumbnailTranslationY = (thumbnailSize - snapshot.height) / 2f
|
||||
}
|
||||
val finalScaleY: Float = thumbnailSize.toFloat() / thumbnail.height
|
||||
val finalScaleY: Float = thumbnailSize.toFloat() / snapshot.height
|
||||
builder.add(
|
||||
ObjectAnimator.ofFloat(
|
||||
thumbnail,
|
||||
TaskThumbnailViewDeprecated.SPLIT_SELECT_TRANSLATE_Y,
|
||||
centerThumbnailTranslationY
|
||||
)
|
||||
ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_Y, centerThumbnailTranslationY)
|
||||
)
|
||||
|
||||
if (!enableOverviewIconMenu()) {
|
||||
// icons are anchored from Gravity.END, so need to use negative translation
|
||||
builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, 0f))
|
||||
}
|
||||
builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_Y, finalScaleY))
|
||||
builder.add(ObjectAnimator.ofFloat(snapshot, View.SCALE_Y, finalScaleY))
|
||||
|
||||
// Reset other dimensions
|
||||
thumbnail.scaleX = 1f
|
||||
builder.add(
|
||||
ObjectAnimator.ofFloat(
|
||||
thumbnail,
|
||||
TaskThumbnailViewDeprecated.SPLIT_SELECT_TRANSLATE_X,
|
||||
0f
|
||||
)
|
||||
)
|
||||
snapshot.scaleX = 1f
|
||||
builder.add(ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_X, 0f))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,29 +31,13 @@ class SplitScreenUtils {
|
||||
null
|
||||
} else {
|
||||
SplitConfigurationOptions.SplitBounds(
|
||||
shellSplitBounds.leftTopBounds, shellSplitBounds.rightBottomBounds,
|
||||
shellSplitBounds.leftTopTaskId, shellSplitBounds.rightBottomTaskId,
|
||||
shellSplitBounds.leftTopBounds,
|
||||
shellSplitBounds.rightBottomBounds,
|
||||
shellSplitBounds.leftTopTaskId,
|
||||
shellSplitBounds.rightBottomTaskId,
|
||||
shellSplitBounds.snapPosition
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Converts the launcher version of SplitBounds to the shell version */
|
||||
@JvmStatic
|
||||
fun convertLauncherSplitBoundsToShell(
|
||||
launcherSplitBounds: SplitConfigurationOptions.SplitBounds?
|
||||
): SplitBounds? {
|
||||
return if (launcherSplitBounds == null) {
|
||||
null
|
||||
} else {
|
||||
SplitBounds(
|
||||
launcherSplitBounds.leftTopBounds,
|
||||
launcherSplitBounds.rightBottomBounds,
|
||||
launcherSplitBounds.leftTopTaskId,
|
||||
launcherSplitBounds.rightBottomTaskId,
|
||||
launcherSplitBounds.snapPosition
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition
|
||||
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
|
||||
import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
|
||||
import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation;
|
||||
import static com.android.quickstep.util.SplitScreenUtils.convertLauncherSplitBoundsToShell;
|
||||
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.content.Context;
|
||||
@@ -247,8 +246,6 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
|
||||
} else {
|
||||
mStagePosition = runningTarget.taskId == splitInfo.leftTopTaskId
|
||||
? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT;
|
||||
mPositionHelper.setSplitBounds(convertLauncherSplitBoundsToShell(mSplitBounds),
|
||||
mStagePosition);
|
||||
}
|
||||
calculateTaskSize();
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
|
||||
}
|
||||
val thumbWidth = (taskSize.width() * scaleWidth).toInt()
|
||||
val thumbHeight = (taskSize.height() * scaleHeight).toInt()
|
||||
it.thumbnailViewDeprecated.measure(
|
||||
it.snapshotView.measure(
|
||||
MeasureSpec.makeMeasureSpec(thumbWidth, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(thumbHeight, MeasureSpec.EXACTLY)
|
||||
)
|
||||
@@ -139,8 +139,8 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
|
||||
var taskY = (positionInParent.y * scaleHeight).toInt()
|
||||
// move task down by margin size
|
||||
taskY += thumbnailTopMarginPx
|
||||
it.thumbnailViewDeprecated.x = taskX.toFloat()
|
||||
it.thumbnailViewDeprecated.y = taskY.toFloat()
|
||||
it.snapshotView.x = taskX.toFloat()
|
||||
it.snapshotView.y = taskY.toFloat()
|
||||
if (DEBUG) {
|
||||
Log.d(
|
||||
TAG,
|
||||
|
||||
@@ -332,12 +332,12 @@ public final class DigitalWellBeingToast {
|
||||
(FrameLayout.LayoutParams) mBanner.getLayoutParams();
|
||||
DeviceProfile deviceProfile = mContainer.getDeviceProfile();
|
||||
layoutParams.bottomMargin = ((ViewGroup.MarginLayoutParams)
|
||||
mTaskView.getFirstThumbnailViewDeprecated().getLayoutParams()).bottomMargin;
|
||||
mTaskView.getFirstSnapshotView().getLayoutParams()).bottomMargin;
|
||||
RecentsPagedOrientationHandler orientationHandler = mTaskView.getPagedOrientationHandler();
|
||||
Pair<Float, Float> translations = orientationHandler
|
||||
.getDwbLayoutTranslations(mTaskView.getMeasuredWidth(),
|
||||
mTaskView.getMeasuredHeight(), mSplitBounds, deviceProfile,
|
||||
mTaskView.getThumbnailViews(), mTask.key.id, mBanner);
|
||||
mTaskView.getSnapshotViews(), mTask.key.id, mBanner);
|
||||
mSplitOffsetTranslationX = translations.first;
|
||||
mSplitOffsetTranslationY = translations.second;
|
||||
updateTranslationY();
|
||||
|
||||
@@ -33,10 +33,8 @@ import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_O
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
|
||||
import com.android.quickstep.TaskOverlayFactory
|
||||
import com.android.quickstep.util.RecentsOrientedState
|
||||
import com.android.quickstep.util.SplitScreenUtils.Companion.convertLauncherSplitBoundsToShell
|
||||
import com.android.quickstep.util.SplitSelectStateController
|
||||
import com.android.systemui.shared.recents.model.Task
|
||||
import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
|
||||
import com.android.systemui.shared.system.InteractionJankMonitorWrapper
|
||||
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition
|
||||
|
||||
@@ -70,36 +68,20 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
|
||||
val initSplitTaskId = getThisTaskCurrentlyInSplitSelection()
|
||||
if (initSplitTaskId == INVALID_TASK_ID) {
|
||||
pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds(
|
||||
taskContainers[0].thumbnailViewDeprecated,
|
||||
taskContainers[1].thumbnailViewDeprecated,
|
||||
taskContainers[0].snapshotView,
|
||||
taskContainers[1].snapshotView,
|
||||
widthSize,
|
||||
heightSize,
|
||||
splitBoundsConfig,
|
||||
container.deviceProfile,
|
||||
layoutDirection == LAYOUT_DIRECTION_RTL
|
||||
)
|
||||
// Should we be having a separate translation step apart from the measuring above?
|
||||
// The following only applies to large screen for now, but for future reference
|
||||
// we'd want to abstract this out in PagedViewHandlers to get the primary/secondary
|
||||
// translation directions
|
||||
taskContainers[0]
|
||||
.thumbnailViewDeprecated
|
||||
.applySplitSelectTranslateX(taskContainers[0].thumbnailViewDeprecated.translationX)
|
||||
taskContainers[0]
|
||||
.thumbnailViewDeprecated
|
||||
.applySplitSelectTranslateY(taskContainers[0].thumbnailViewDeprecated.translationY)
|
||||
taskContainers[1]
|
||||
.thumbnailViewDeprecated
|
||||
.applySplitSelectTranslateX(taskContainers[1].thumbnailViewDeprecated.translationX)
|
||||
taskContainers[1]
|
||||
.thumbnailViewDeprecated
|
||||
.applySplitSelectTranslateY(taskContainers[1].thumbnailViewDeprecated.translationY)
|
||||
} else {
|
||||
// Currently being split with this taskView, let the non-split selected thumbnail
|
||||
// take up full thumbnail area
|
||||
taskContainers
|
||||
.firstOrNull { it.task.key.id != initSplitTaskId }
|
||||
?.thumbnailViewDeprecated
|
||||
?.snapshotView
|
||||
?.measure(
|
||||
widthMeasureSpec,
|
||||
MeasureSpec.makeMeasureSpec(
|
||||
@@ -147,23 +129,7 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
|
||||
)
|
||||
taskContainers.forEach { it.bind() }
|
||||
|
||||
this.splitBoundsConfig =
|
||||
splitBoundsConfig?.also {
|
||||
taskContainers[0]
|
||||
.thumbnailViewDeprecated
|
||||
.previewPositionHelper
|
||||
.setSplitBounds(
|
||||
convertLauncherSplitBoundsToShell(it),
|
||||
PreviewPositionHelper.STAGE_POSITION_TOP_OR_LEFT
|
||||
)
|
||||
taskContainers[1]
|
||||
.thumbnailViewDeprecated
|
||||
.previewPositionHelper
|
||||
.setSplitBounds(
|
||||
convertLauncherSplitBoundsToShell(it),
|
||||
PreviewPositionHelper.STAGE_POSITION_BOTTOM_OR_RIGHT
|
||||
)
|
||||
}
|
||||
this.splitBoundsConfig = splitBoundsConfig
|
||||
taskContainers.forEach { it.digitalWellBeingToast?.setSplitBounds(splitBoundsConfig) }
|
||||
setOrientationState(orientedState)
|
||||
}
|
||||
@@ -230,8 +196,8 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
|
||||
taskContainers[0].iconView.asView(),
|
||||
taskContainers[1].iconView.asView(),
|
||||
taskIconHeight,
|
||||
taskContainers[0].thumbnailViewDeprecated.measuredWidth,
|
||||
taskContainers[0].thumbnailViewDeprecated.measuredHeight,
|
||||
taskContainers[0].snapshotView.measuredWidth,
|
||||
taskContainers[0].snapshotView.measuredHeight,
|
||||
measuredHeight,
|
||||
measuredWidth,
|
||||
isRtl,
|
||||
@@ -326,7 +292,7 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
|
||||
// Check which of the two apps was selected
|
||||
if (
|
||||
taskContainers[1].iconView.asView().containsPoint(lastTouchDownPosition) ||
|
||||
taskContainers[1].thumbnailViewDeprecated.containsPoint(lastTouchDownPosition)
|
||||
taskContainers[1].snapshotView.containsPoint(lastTouchDownPosition)
|
||||
) {
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -1826,8 +1826,13 @@ public abstract class RecentsView<CONTAINER_TYPE extends Context & RecentsViewCo
|
||||
((GroupedTaskView) taskView).bind(leftTopTask, rightBottomTask, mOrientationState,
|
||||
mTaskOverlayFactory, groupTask.mSplitBounds);
|
||||
} else if (taskView instanceof DesktopTaskView) {
|
||||
((DesktopTaskView) taskView).bind(((DesktopTask) groupTask).tasks,
|
||||
mOrientationState, mTaskOverlayFactory);
|
||||
// Minimized tasks should not be shown in Overview
|
||||
List<Task> nonMinimizedTasks =
|
||||
((DesktopTask) groupTask).tasks.stream()
|
||||
.filter(task -> !task.isMinimized)
|
||||
.toList();
|
||||
((DesktopTaskView) taskView).bind(nonMinimizedTasks, mOrientationState,
|
||||
mTaskOverlayFactory);
|
||||
mDesktopTaskView = (DesktopTaskView) taskView;
|
||||
} else {
|
||||
Task task = groupTask.task1.key.id == stagedTaskIdToBeRemoved ? groupTask.task2
|
||||
|
||||
@@ -238,12 +238,12 @@ public class TaskMenuView extends AbstractFloatingView {
|
||||
mContainer.getDragLayer().getDescendantRectRelativeToSelf(
|
||||
enableOverviewIconMenu()
|
||||
? getIconView().findViewById(R.id.icon_view_menu_anchor)
|
||||
: taskContainer.getThumbnailViewDeprecated(),
|
||||
: taskContainer.getSnapshotView(),
|
||||
sTempRect);
|
||||
Rect insets = mContainer.getDragLayer().getInsets();
|
||||
BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams();
|
||||
params.width = orientationHandler.getTaskMenuWidth(
|
||||
taskContainer.getThumbnailViewDeprecated(), deviceProfile,
|
||||
taskContainer.getSnapshotView(), deviceProfile,
|
||||
taskContainer.getStagePosition());
|
||||
// Gravity set to Left instead of Start as sTempRect.left measures Left distance not Start
|
||||
params.gravity = Gravity.LEFT;
|
||||
@@ -277,10 +277,10 @@ public class TaskMenuView extends AbstractFloatingView {
|
||||
// Margin that insets the menuView inside the taskView
|
||||
float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin);
|
||||
setTranslationX(orientationHandler.getTaskMenuX(thumbnailAlignedX,
|
||||
mTaskContainer.getThumbnailViewDeprecated(), deviceProfile, taskInsetMargin,
|
||||
mTaskContainer.getSnapshotView(), deviceProfile, taskInsetMargin,
|
||||
getIconView()));
|
||||
setTranslationY(orientationHandler.getTaskMenuY(
|
||||
thumbnailAlignedY, mTaskContainer.getThumbnailViewDeprecated(),
|
||||
thumbnailAlignedY, mTaskContainer.getSnapshotView(),
|
||||
mTaskContainer.getStagePosition(), this, taskInsetMargin,
|
||||
getIconView()));
|
||||
}
|
||||
@@ -316,7 +316,7 @@ public class TaskMenuView extends AbstractFloatingView {
|
||||
.createRevealAnimator(this, closing, revealAnimationStartProgress);
|
||||
mRevealAnimator.setInterpolator(enableOverviewIconMenu() ? Interpolators.EMPHASIZED
|
||||
: Interpolators.DECELERATE);
|
||||
|
||||
AnimatorSet.Builder openCloseAnimatorBuilder = mOpenCloseAnimator.play(mRevealAnimator);
|
||||
if (enableOverviewIconMenu()) {
|
||||
IconAppChipView iconAppChip = (IconAppChipView) mTaskContainer.getIconView().asView();
|
||||
|
||||
@@ -334,11 +334,13 @@ public class TaskMenuView extends AbstractFloatingView {
|
||||
closing ? mMenuTranslationYBeforeOpen
|
||||
: mMenuTranslationYBeforeOpen + additionalTranslationY);
|
||||
translationYAnim.setInterpolator(EMPHASIZED);
|
||||
openCloseAnimatorBuilder.with(translationYAnim);
|
||||
|
||||
ObjectAnimator menuTranslationYAnim = ObjectAnimator.ofFloat(
|
||||
iconAppChip.getMenuTranslationY(),
|
||||
MULTI_PROPERTY_VALUE, closing ? 0 : additionalTranslationY);
|
||||
menuTranslationYAnim.setInterpolator(EMPHASIZED);
|
||||
openCloseAnimatorBuilder.with(menuTranslationYAnim);
|
||||
|
||||
float additionalTranslationX = 0;
|
||||
if (mContainer.getDeviceProfile().isLandscape
|
||||
@@ -354,20 +356,15 @@ public class TaskMenuView extends AbstractFloatingView {
|
||||
closing ? mMenuTranslationXBeforeOpen
|
||||
: mMenuTranslationXBeforeOpen - additionalTranslationX);
|
||||
translationXAnim.setInterpolator(EMPHASIZED);
|
||||
openCloseAnimatorBuilder.with(translationXAnim);
|
||||
|
||||
ObjectAnimator menuTranslationXAnim = ObjectAnimator.ofFloat(
|
||||
iconAppChip.getMenuTranslationX(),
|
||||
MULTI_PROPERTY_VALUE, closing ? 0 : -additionalTranslationX);
|
||||
menuTranslationXAnim.setInterpolator(EMPHASIZED);
|
||||
|
||||
mOpenCloseAnimator.playTogether(translationYAnim, translationXAnim,
|
||||
menuTranslationXAnim, menuTranslationYAnim);
|
||||
openCloseAnimatorBuilder.with(menuTranslationXAnim);
|
||||
}
|
||||
mOpenCloseAnimator.playTogether(mRevealAnimator,
|
||||
ObjectAnimator.ofFloat(
|
||||
mTaskContainer.getThumbnailViewDeprecated(), DIM_ALPHA,
|
||||
closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA),
|
||||
ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1));
|
||||
openCloseAnimatorBuilder.with(ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1));
|
||||
if (enableRefactorTaskThumbnail()) {
|
||||
mRevealAnimator.addUpdateListener(animation -> {
|
||||
float animatedFraction = animation.getAnimatedFraction();
|
||||
@@ -375,6 +372,10 @@ public class TaskMenuView extends AbstractFloatingView {
|
||||
mTaskContainer.getTaskContainerData()
|
||||
.getTaskMenuOpenProgress().setValue(openProgress);
|
||||
});
|
||||
} else {
|
||||
openCloseAnimatorBuilder.with(ObjectAnimator.ofFloat(
|
||||
mTaskContainer.getThumbnailViewDeprecated(), DIM_ALPHA,
|
||||
closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA));
|
||||
}
|
||||
mOpenCloseAnimator.addListener(new AnimationSuccessListener() {
|
||||
@Override
|
||||
|
||||
@@ -99,36 +99,6 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
|
||||
}
|
||||
};
|
||||
|
||||
/** Use to animate thumbnail translationX while first app in split selection is initiated */
|
||||
public static final Property<TaskThumbnailViewDeprecated, Float> SPLIT_SELECT_TRANSLATE_X =
|
||||
new FloatProperty<TaskThumbnailViewDeprecated>("splitSelectTranslateX") {
|
||||
@Override
|
||||
public void setValue(TaskThumbnailViewDeprecated thumbnail,
|
||||
float splitSelectTranslateX) {
|
||||
thumbnail.applySplitSelectTranslateX(splitSelectTranslateX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(TaskThumbnailViewDeprecated thumbnailView) {
|
||||
return thumbnailView.mSplitSelectTranslateX;
|
||||
}
|
||||
};
|
||||
|
||||
/** Use to animate thumbnail translationY while first app in split selection is initiated */
|
||||
public static final Property<TaskThumbnailViewDeprecated, Float> SPLIT_SELECT_TRANSLATE_Y =
|
||||
new FloatProperty<TaskThumbnailViewDeprecated>("splitSelectTranslateY") {
|
||||
@Override
|
||||
public void setValue(TaskThumbnailViewDeprecated thumbnail,
|
||||
float splitSelectTranslateY) {
|
||||
thumbnail.applySplitSelectTranslateY(splitSelectTranslateY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(TaskThumbnailViewDeprecated thumbnailView) {
|
||||
return thumbnailView.mSplitSelectTranslateY;
|
||||
}
|
||||
};
|
||||
|
||||
private final RecentsViewContainer mContainer;
|
||||
private TaskOverlay<?> mOverlay;
|
||||
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
@@ -160,8 +130,6 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
|
||||
private boolean mOverlayEnabled;
|
||||
/** Used as a placeholder when the original thumbnail animates out to. */
|
||||
private boolean mShowSplashForSplitSelection;
|
||||
private float mSplitSelectTranslateX;
|
||||
private float mSplitSelectTranslateY;
|
||||
|
||||
public TaskThumbnailViewDeprecated(Context context) {
|
||||
this(context, null);
|
||||
@@ -415,31 +383,6 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
|
||||
}
|
||||
}
|
||||
|
||||
/** See {@link #SPLIT_SELECT_TRANSLATE_X} */
|
||||
protected void applySplitSelectTranslateX(float splitSelectTranslateX) {
|
||||
mSplitSelectTranslateX = splitSelectTranslateX;
|
||||
applyTranslateX();
|
||||
}
|
||||
|
||||
/** See {@link #SPLIT_SELECT_TRANSLATE_Y} */
|
||||
protected void applySplitSelectTranslateY(float splitSelectTranslateY) {
|
||||
mSplitSelectTranslateY = splitSelectTranslateY;
|
||||
applyTranslateY();
|
||||
}
|
||||
|
||||
private void applyTranslateX() {
|
||||
setTranslationX(mSplitSelectTranslateX);
|
||||
}
|
||||
|
||||
private void applyTranslateY() {
|
||||
setTranslationY(mSplitSelectTranslateY);
|
||||
}
|
||||
|
||||
protected void resetViewTransforms() {
|
||||
mSplitSelectTranslateX = 0;
|
||||
mSplitSelectTranslateY = 0;
|
||||
}
|
||||
|
||||
public TaskView getTaskView() {
|
||||
return (TaskView) getParent();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,9 @@ import android.annotation.IdRes
|
||||
import android.app.ActivityOptions
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Insets
|
||||
import android.graphics.PointF
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
@@ -138,8 +140,8 @@ constructor(
|
||||
/** Returns a copy of integer array containing taskIds of all tasks in the TaskView. */
|
||||
get() = taskContainers.map { it.task.key.id }.toIntArray()
|
||||
|
||||
val thumbnailViews: Array<TaskThumbnailViewDeprecated>
|
||||
get() = taskContainers.map { it.thumbnailViewDeprecated }.toTypedArray()
|
||||
val snapshotViews: Array<View>
|
||||
get() = taskContainers.map { it.snapshotView }.toTypedArray()
|
||||
|
||||
val isGridTask: Boolean
|
||||
/** Returns whether the task is part of overview grid and not being focused. */
|
||||
@@ -170,6 +172,11 @@ constructor(
|
||||
/** Returns the first thumbnailView of the TaskView. */
|
||||
get() = taskContainers[0].thumbnailViewDeprecated
|
||||
|
||||
@get:Deprecated("Use [taskContainers] instead.")
|
||||
val firstSnapshotView: View
|
||||
/** Returns the first snapshotView of the TaskView. */
|
||||
get() = taskContainers[0].snapshotView
|
||||
|
||||
@get:Deprecated("Use [taskContainers] instead.")
|
||||
val firstItemInfo: ItemInfo
|
||||
get() = taskContainers[0].itemInfo
|
||||
@@ -1197,10 +1204,10 @@ constructor(
|
||||
this,
|
||||
container.task,
|
||||
container.iconView.drawable,
|
||||
container.thumbnailViewDeprecated,
|
||||
container.thumbnailViewDeprecated.thumbnail, /* intent */
|
||||
null, /* user */
|
||||
null,
|
||||
container.snapshotView,
|
||||
container.thumbnail,
|
||||
/* intent */ null,
|
||||
/* user */ null,
|
||||
container.itemInfo
|
||||
)
|
||||
}
|
||||
@@ -1512,6 +1519,10 @@ constructor(
|
||||
gridTranslationY = 0f
|
||||
boxTranslationY = 0f
|
||||
nonGridPivotTranslationX = 0f
|
||||
taskContainers.forEach {
|
||||
it.snapshotView.translationX = 0f
|
||||
it.snapshotView.translationY = 0f
|
||||
}
|
||||
resetViewTransforms()
|
||||
}
|
||||
|
||||
@@ -1537,10 +1548,6 @@ constructor(
|
||||
alpha = stableAlpha
|
||||
setIconScaleAndDim(1f)
|
||||
setColorTint(0f, 0)
|
||||
if (!enableRefactorTaskThumbnail()) {
|
||||
// TODO(b/335399428) add split select functionality to new TTV
|
||||
taskContainers.forEach { it.thumbnailViewDeprecated.resetViewTransforms() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getGridTrans(endTranslation: Float) =
|
||||
@@ -1624,6 +1631,18 @@ constructor(
|
||||
val snapshotView: View
|
||||
get() = thumbnailView ?: thumbnailViewDeprecated
|
||||
|
||||
// TODO(b/349120849): Extract ThumbnailData from TaskContainerData/TaskThumbnailViewModel
|
||||
val thumbnail: Bitmap?
|
||||
get() = thumbnailViewDeprecated.thumbnail
|
||||
|
||||
// TODO(b/349120849): Extract ThumbnailData from TaskContainerData/TaskThumbnailViewModel
|
||||
val isRealSnapshot: Boolean
|
||||
get() = thumbnailViewDeprecated.isRealSnapshot()
|
||||
|
||||
// TODO(b/349120849): Extract ThumbnailData from TaskContainerData/TaskThumbnailViewModel
|
||||
val scaledInsets: Insets
|
||||
get() = thumbnailViewDeprecated.scaledInsets
|
||||
|
||||
/** Builds proto for logging */
|
||||
val itemInfo: WorkspaceItemInfo
|
||||
get() =
|
||||
|
||||
+8
-9
@@ -31,14 +31,15 @@ import com.android.quickstep.AllAppsActionManager
|
||||
import com.android.quickstep.TouchInteractionService
|
||||
import com.android.quickstep.TouchInteractionService.TISBinder
|
||||
import org.junit.Assume.assumeTrue
|
||||
import org.junit.rules.MethodRule
|
||||
import org.junit.runners.model.FrameworkMethod
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.Description
|
||||
import org.junit.runners.model.Statement
|
||||
|
||||
/**
|
||||
* Manages the Taskbar lifecycle for unit tests.
|
||||
*
|
||||
* Tests need to provide their target [context] through the constructor.
|
||||
* Tests should pass in themselves as [testInstance]. They also need to provide their target
|
||||
* [context] through the constructor.
|
||||
*
|
||||
* See [InjectController] for grabbing controller(s) under test with minimal boilerplate.
|
||||
*
|
||||
@@ -61,12 +62,11 @@ import org.junit.runners.model.Statement
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class TaskbarUnitTestRule(private val context: Context) : MethodRule {
|
||||
class TaskbarUnitTestRule(private val testInstance: Any, private val context: Context) : TestRule {
|
||||
private val instrumentation = InstrumentationRegistry.getInstrumentation()
|
||||
private val serviceTestRule = ServiceTestRule()
|
||||
|
||||
private lateinit var taskbarManager: TaskbarManager
|
||||
private lateinit var target: Any
|
||||
|
||||
val activityContext: TaskbarActivityContext
|
||||
get() {
|
||||
@@ -74,10 +74,9 @@ class TaskbarUnitTestRule(private val context: Context) : MethodRule {
|
||||
?: throw RuntimeException("Failed to obtain TaskbarActivityContext.")
|
||||
}
|
||||
|
||||
override fun apply(base: Statement, method: FrameworkMethod, target: Any): Statement {
|
||||
override fun apply(base: Statement, description: Description): Statement {
|
||||
return object : Statement() {
|
||||
override fun evaluate() {
|
||||
this@TaskbarUnitTestRule.target = target
|
||||
|
||||
instrumentation.runOnMainSync {
|
||||
assumeTrue(
|
||||
@@ -141,11 +140,11 @@ class TaskbarUnitTestRule(private val context: Context) : MethodRule {
|
||||
private fun injectControllers() {
|
||||
val controllers = activityContext.controllers
|
||||
val controllerFieldsByType = controllers.javaClass.fields.associateBy { it.type }
|
||||
target.javaClass.fields
|
||||
testInstance.javaClass.fields
|
||||
.filter { it.isAnnotationPresent(InjectController::class.java) }
|
||||
.forEach {
|
||||
it.set(
|
||||
target,
|
||||
testInstance,
|
||||
controllerFieldsByType[it.type]?.get(controllers)
|
||||
?: throw NoSuchElementException("Failed to find controller for ${it.type}"),
|
||||
)
|
||||
|
||||
+2
-1
@@ -42,7 +42,8 @@ import org.junit.runner.RunWith
|
||||
@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
|
||||
class TaskbarAllAppsControllerTest {
|
||||
|
||||
@get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule(getInstrumentation().targetContext)
|
||||
@get:Rule
|
||||
val taskbarUnitTestRule = TaskbarUnitTestRule(this, getInstrumentation().targetContext)
|
||||
@get:Rule val animatorTestRule = AnimatorTestRule(this)
|
||||
|
||||
@InjectController lateinit var allAppsController: TaskbarAllAppsController
|
||||
|
||||
+2
-1
@@ -41,7 +41,8 @@ import org.junit.runner.RunWith
|
||||
@EmulatedDevices(["pixelFoldable2023"])
|
||||
class TaskbarOverlayControllerTest {
|
||||
|
||||
@get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule(getInstrumentation().targetContext)
|
||||
@get:Rule
|
||||
val taskbarUnitTestRule = TaskbarUnitTestRule(this, getInstrumentation().targetContext)
|
||||
@InjectController lateinit var overlayController: TaskbarOverlayController
|
||||
|
||||
private val taskbarContext: TaskbarActivityContext
|
||||
|
||||
+3
-5
@@ -33,7 +33,6 @@ import com.android.launcher3.taskbar.TaskbarActivityContext
|
||||
import com.android.launcher3.util.SplitConfigurationOptions
|
||||
import com.android.quickstep.views.GroupedTaskView
|
||||
import com.android.quickstep.views.IconView
|
||||
import com.android.quickstep.views.TaskThumbnailViewDeprecated
|
||||
import com.android.quickstep.views.TaskView
|
||||
import com.android.quickstep.views.TaskView.TaskContainer
|
||||
import com.android.systemui.shared.recents.model.Task
|
||||
@@ -59,7 +58,7 @@ class SplitAnimationControllerTest {
|
||||
private val mockSplitSelectStateController: SplitSelectStateController = mock()
|
||||
// TaskView
|
||||
private val mockTaskView: TaskView = mock()
|
||||
private val mockThumbnailView: TaskThumbnailViewDeprecated = mock()
|
||||
private val mockSnapshotView: View = mock()
|
||||
private val mockBitmap: Bitmap = mock()
|
||||
private val mockIconView: IconView = mock()
|
||||
private val mockTaskViewDrawable: Drawable = mock()
|
||||
@@ -87,8 +86,8 @@ class SplitAnimationControllerTest {
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
whenever(mockTaskContainer.thumbnailViewDeprecated).thenReturn(mockThumbnailView)
|
||||
whenever(mockThumbnailView.thumbnail).thenReturn(mockBitmap)
|
||||
whenever(mockTaskContainer.snapshotView).thenReturn(mockSnapshotView)
|
||||
whenever(mockTaskContainer.thumbnail).thenReturn(mockBitmap)
|
||||
whenever(mockTaskContainer.iconView).thenReturn(mockIconView)
|
||||
whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable)
|
||||
whenever(mockTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer })
|
||||
@@ -180,7 +179,6 @@ class SplitAnimationControllerTest {
|
||||
|
||||
whenever(mockTaskContainer.task).thenReturn(mockTask)
|
||||
whenever(mockTaskContainer.iconView).thenReturn(mockIconView)
|
||||
whenever(mockTaskContainer.thumbnailViewDeprecated).thenReturn(mockThumbnailView)
|
||||
whenever(mockTask.getKey()).thenReturn(mockTaskKey)
|
||||
whenever(mockTaskKey.getId()).thenReturn(taskId)
|
||||
whenever(mockSplitSelectStateController.initialTaskId).thenReturn(taskId)
|
||||
|
||||
@@ -32,6 +32,8 @@ import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.launcher3.util.LooperExecutor;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.wm.shell.util.GroupedRecentTaskInfo;
|
||||
|
||||
import org.junit.Before;
|
||||
@@ -40,8 +42,11 @@ import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SmallTest
|
||||
public class RecentTasksListTest {
|
||||
@@ -104,4 +109,52 @@ public class RecentTasksListTest {
|
||||
assertEquals(taskDescription, taskList.get(0).task1.taskDescription.getLabel());
|
||||
assertNull(taskList.get(0).task2.taskDescription.getLabel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadTasksInBackground_freeformTask_createsDesktopTask() {
|
||||
ActivityManager.RecentTaskInfo[] tasks = {
|
||||
createRecentTaskInfo(1 /* taskId */),
|
||||
createRecentTaskInfo(4 /* taskId */),
|
||||
createRecentTaskInfo(5 /* taskId */)};
|
||||
GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forFreeformTasks(
|
||||
tasks, Collections.emptySet() /* minimizedTaskIds */);
|
||||
when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
|
||||
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
|
||||
|
||||
List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(
|
||||
Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */);
|
||||
|
||||
assertEquals(1, taskList.size());
|
||||
assertEquals(TaskView.Type.DESKTOP, taskList.get(0).taskViewType);
|
||||
List<Task> actualFreeformTasks = taskList.get(0).getTasks();
|
||||
assertEquals(3, actualFreeformTasks.size());
|
||||
assertEquals(1, actualFreeformTasks.get(0).key.id);
|
||||
assertEquals(4, actualFreeformTasks.get(1).key.id);
|
||||
assertEquals(5, actualFreeformTasks.get(2).key.id);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_doesNotCreateDesktopTask() {
|
||||
ActivityManager.RecentTaskInfo[] tasks = {
|
||||
createRecentTaskInfo(1 /* taskId */),
|
||||
createRecentTaskInfo(4 /* taskId */),
|
||||
createRecentTaskInfo(5 /* taskId */)};
|
||||
Set<Integer> minimizedTaskIds =
|
||||
Arrays.stream(new Integer[]{1, 4, 5}).collect(Collectors.toSet());
|
||||
GroupedRecentTaskInfo recentTaskInfos =
|
||||
GroupedRecentTaskInfo.forFreeformTasks(tasks, minimizedTaskIds);
|
||||
when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
|
||||
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
|
||||
|
||||
List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(
|
||||
Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */);
|
||||
|
||||
assertEquals(0, taskList.size());
|
||||
}
|
||||
|
||||
private ActivityManager.RecentTaskInfo createRecentTaskInfo(int taskId) {
|
||||
ActivityManager.RecentTaskInfo recentTaskInfo = new ActivityManager.RecentTaskInfo();
|
||||
recentTaskInfo.taskId = taskId;
|
||||
return recentTaskInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,8 +331,21 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView<BaseActivity>
|
||||
* status bar, into account.
|
||||
*/
|
||||
protected void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int widthUsed = getInsetsWidth();
|
||||
|
||||
DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
|
||||
measureChildWithMargins(mContent, widthMeasureSpec,
|
||||
widthUsed, heightMeasureSpec, deviceProfile.bottomSheetTopPadding);
|
||||
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
|
||||
MeasureSpec.getSize(heightMeasureSpec));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the width used on left and right by the insets / padding.
|
||||
*/
|
||||
protected int getInsetsWidth() {
|
||||
int widthUsed;
|
||||
DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
|
||||
if (deviceProfile.isTablet) {
|
||||
widthUsed = Math.max(2 * getTabletHorizontalMargin(deviceProfile),
|
||||
2 * (mInsets.left + mInsets.right));
|
||||
@@ -343,11 +356,7 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView<BaseActivity>
|
||||
widthUsed = Math.max(padding.left + padding.right,
|
||||
2 * (mInsets.left + mInsets.right));
|
||||
}
|
||||
|
||||
measureChildWithMargins(mContent, widthMeasureSpec,
|
||||
widthUsed, heightMeasureSpec, deviceProfile.bottomSheetTopPadding);
|
||||
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
|
||||
MeasureSpec.getSize(heightMeasureSpec));
|
||||
return widthUsed;
|
||||
}
|
||||
|
||||
/** Returns the horizontal margins to be applied to the widget sheet. **/
|
||||
|
||||
@@ -55,7 +55,6 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.launcher3.BaseActivity;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.compat.AccessibilityManagerCompat;
|
||||
@@ -416,19 +415,18 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int availableWidth = MeasureSpec.getSize(widthMeasureSpec);
|
||||
updateMaxSpansPerRow(availableWidth);
|
||||
doMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
if (updateMaxSpansPerRow()) {
|
||||
doMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns {@code true} if the max spans have been updated. */
|
||||
private boolean updateMaxSpansPerRow() {
|
||||
if (getMeasuredWidth() == 0) return false;
|
||||
|
||||
@Px int maxHorizontalSpan = getContentView().getMeasuredWidth()
|
||||
- (2 * mContentHorizontalMargin);
|
||||
/** Returns {@code true} if the max spans have been updated.
|
||||
*
|
||||
* @param availableWidth Total width available within parent (includes insets).
|
||||
*/
|
||||
private void updateMaxSpansPerRow(int availableWidth) {
|
||||
@Px int maxHorizontalSpan = getAvailableWidthForSuggestions(
|
||||
availableWidth - getInsetsWidth());
|
||||
if (mMaxSpanPerRow != maxHorizontalSpan) {
|
||||
mMaxSpanPerRow = maxHorizontalSpan;
|
||||
mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(
|
||||
@@ -439,16 +437,15 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(
|
||||
maxHorizontalSpan);
|
||||
}
|
||||
onRecommendedWidgetsBound();
|
||||
return true;
|
||||
post(this::onRecommendedWidgetsBound);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected View getContentView() {
|
||||
return mHasWorkProfile
|
||||
? mViewPager
|
||||
: mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView;
|
||||
/**
|
||||
* Returns the width available to display suggestions.
|
||||
*/
|
||||
protected int getAvailableWidthForSuggestions(int pickerAvailableWidth) {
|
||||
return pickerAvailableWidth - (2 * mContentHorizontalMargin);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -493,7 +490,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
.mWidgetsListAdapter.hasVisibleEntries());
|
||||
if (mIsNoWidgetsViewNeeded != isNoWidgetsViewNeeded) {
|
||||
mIsNoWidgetsViewNeeded = isNoWidgetsViewNeeded;
|
||||
onRecommendedWidgetsBound();
|
||||
post(this::onRecommendedWidgetsBound);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,7 +546,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.setVisibility(GONE);
|
||||
// Visibility of recommended widgets, recycler views and headers are handled in methods
|
||||
// below.
|
||||
onRecommendedWidgetsBound();
|
||||
post(this::onRecommendedWidgetsBound);
|
||||
onWidgetsBound();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.launcher3.widget.picker;
|
||||
|
||||
import static android.animation.ValueAnimator.areAnimatorsEnabled;
|
||||
|
||||
import static com.android.launcher3.widget.picker.WidgetsListAdapter.VIEW_TYPE_WIDGETS_LIST;
|
||||
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
@@ -26,6 +28,14 @@ public class WidgetsListItemAnimator extends DefaultItemAnimator {
|
||||
public static final int MOVE_DURATION_MS = 90;
|
||||
public static final int ADD_DURATION_MS = 120;
|
||||
|
||||
// DefaultItemAnimator runs change and move animations before running add animations (i.e.
|
||||
// before expanded list item's content start animating to become visible on screen).
|
||||
public static final int WIDGET_LIST_ITEM_APPEARANCE_START_DELAY =
|
||||
areAnimatorsEnabled() ? (CHANGE_DURATION_MS + MOVE_DURATION_MS) : 0;
|
||||
// Delay after which all item animations are ran and list item's content is visible.
|
||||
public static final int WIDGET_LIST_ITEM_APPEARANCE_DELAY =
|
||||
WIDGET_LIST_ITEM_APPEARANCE_START_DELAY + ADD_DURATION_MS;
|
||||
|
||||
public WidgetsListItemAnimator() {
|
||||
super();
|
||||
|
||||
|
||||
@@ -15,10 +15,7 @@
|
||||
*/
|
||||
package com.android.launcher3.widget.picker;
|
||||
|
||||
import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.CHANGE_DURATION_MS;
|
||||
import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.MOVE_DURATION_MS;
|
||||
|
||||
import static android.animation.ValueAnimator.areAnimatorsEnabled;
|
||||
import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.WIDGET_LIST_ITEM_APPEARANCE_START_DELAY;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
@@ -157,8 +154,7 @@ public final class WidgetsListTableViewHolderBinder
|
||||
// Pass resize delay to let the "move" and "change" animations run before resizing the
|
||||
// row.
|
||||
tableRow.setupRow(widgetItems.size(),
|
||||
/*resizeDelayMs=*/
|
||||
areAnimatorsEnabled() ? (CHANGE_DURATION_MS + MOVE_DURATION_MS) : 0);
|
||||
/*resizeDelayMs=*/ WIDGET_LIST_ITEM_APPEARANCE_START_DELAY);
|
||||
if (tableRow.getChildCount() > widgetItems.size()) {
|
||||
for (int j = widgetItems.size(); j < tableRow.getChildCount(); j++) {
|
||||
tableRow.getChildAt(j).setVisibility(View.GONE);
|
||||
|
||||
@@ -21,6 +21,7 @@ import static com.android.launcher3.UtilitiesKt.CLIP_CHILDREN_FALSE_MODIFIER;
|
||||
import static com.android.launcher3.UtilitiesKt.CLIP_TO_PADDING_FALSE_MODIFIER;
|
||||
import static com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree;
|
||||
import static com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree;
|
||||
import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.WIDGET_LIST_ITEM_APPEARANCE_DELAY;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
@@ -31,6 +32,7 @@ import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
@@ -281,10 +283,19 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
|
||||
mRightPane.removeAllViews();
|
||||
mRightPane.addView(mWidgetRecommendationsContainer);
|
||||
mRightPaneScrollView.setScrollY(0);
|
||||
mRightPane.setAccessibilityPaneTitle(suggestionsRightPaneTitle);
|
||||
mSuggestedWidgetsPackageUserKey = PackageUserKey.fromPackageItemInfo(packageItemInfo);
|
||||
final boolean isChangingHeaders = mSelectedHeader == null
|
||||
|| !mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey);
|
||||
// If the initial focus view is still focused, it is likely a programmatic header
|
||||
// click.
|
||||
if (mSelectedHeader != null
|
||||
&& !getAccessibilityInitialFocusView().isAccessibilityFocused()) {
|
||||
post(() -> {
|
||||
mRightPaneScrollView.setAccessibilityPaneTitle(suggestionsRightPaneTitle);
|
||||
mRightPaneScrollView.performAccessibilityAction(
|
||||
AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
|
||||
});
|
||||
}
|
||||
if (isChangingHeaders) {
|
||||
// If switching from another header, unselect any WidgetCells. This is necessary
|
||||
// because we do not clear/recycle the WidgetCells in the recommendations container
|
||||
@@ -296,7 +307,6 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
|
||||
mSelectedHeader = mSuggestedWidgetsPackageUserKey;
|
||||
});
|
||||
mSuggestedWidgetsContainer.addView(mSuggestedWidgetsHeader);
|
||||
mRightPane.setAccessibilityPaneTitle(suggestionsRightPaneTitle);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -312,6 +322,30 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
|
||||
* RECOMMENDATION_SECTION_HEIGHT_RATIO_TWO_PANE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Px
|
||||
protected int getAvailableWidthForSuggestions(int pickerAvailableWidth) {
|
||||
int rightPaneWidth = (int) Math.ceil(0.67 * pickerAvailableWidth);
|
||||
|
||||
if (mDeviceProfile.isTwoPanels && enableUnfoldedTwoPanePicker()) {
|
||||
// See onLayout
|
||||
int leftPaneWidth = (int) (0.33 * pickerAvailableWidth);
|
||||
@Px int minLeftPaneWidthPx = Utilities.dpToPx(MINIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP);
|
||||
@Px int maxLeftPaneWidthPx = Utilities.dpToPx(MAXIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP);
|
||||
if (leftPaneWidth < minLeftPaneWidthPx) {
|
||||
leftPaneWidth = minLeftPaneWidthPx;
|
||||
} else if (leftPaneWidth > maxLeftPaneWidthPx) {
|
||||
leftPaneWidth = maxLeftPaneWidthPx;
|
||||
}
|
||||
rightPaneWidth = pickerAvailableWidth - leftPaneWidth;
|
||||
}
|
||||
|
||||
// Since suggestions are shown in right pane, the available width is 2/3 of total width of
|
||||
// bottom sheet.
|
||||
return rightPaneWidth - getResources().getDimensionPixelSize(
|
||||
R.dimen.widget_list_horizontal_margin_two_pane); // right pane end margin.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivePageChanged(int currentActivePage) {
|
||||
super.onActivePageChanged(currentActivePage);
|
||||
@@ -323,12 +357,14 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
|
||||
|
||||
mActivePage = currentActivePage;
|
||||
|
||||
if (mSuggestedWidgetsHeader == null) {
|
||||
mAdapters.get(currentActivePage).mWidgetsListAdapter.selectFirstHeaderEntry();
|
||||
mAdapters.get(currentActivePage).mWidgetsRecyclerView.scrollToTop();
|
||||
} else if (currentActivePage == PERSONAL_TAB || currentActivePage == WORK_TAB) {
|
||||
mSuggestedWidgetsHeader.callOnClick();
|
||||
}
|
||||
// When using talkback, swiping left while on right pane, should navigate to the widgets
|
||||
// list on left.
|
||||
mAdapters.get(mActivePage).mWidgetsRecyclerView.setAccessibilityTraversalBefore(
|
||||
mRightPaneScrollView.getId());
|
||||
|
||||
// On page change, select the first item in the list to show in the right pane.
|
||||
mAdapters.get(currentActivePage).mWidgetsListAdapter.selectFirstHeaderEntry();
|
||||
mAdapters.get(currentActivePage).mWidgetsRecyclerView.scrollToTop();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -372,17 +408,16 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getContentView() {
|
||||
return mRightPane;
|
||||
}
|
||||
|
||||
private HeaderChangeListener getHeaderChangeListener() {
|
||||
return new HeaderChangeListener() {
|
||||
@Override
|
||||
public void onHeaderChanged(@NonNull PackageUserKey selectedHeader) {
|
||||
final boolean isSameHeader = mSelectedHeader != null
|
||||
&& mSelectedHeader.equals(selectedHeader);
|
||||
// If the initial focus view is still focused, it is likely a programmatic header
|
||||
// click.
|
||||
final boolean isUserClick = mSelectedHeader != null
|
||||
&& !getAccessibilityInitialFocusView().isAccessibilityFocused();
|
||||
mSelectedHeader = selectedHeader;
|
||||
WidgetsListContentEntry contentEntry = mActivityContext.getPopupDataProvider()
|
||||
.getSelectedAppWidgets(selectedHeader);
|
||||
@@ -427,11 +462,14 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
|
||||
};
|
||||
mRightPane.removeAllViews();
|
||||
mRightPane.addView(widgetsRowViewHolder.itemView);
|
||||
if (isUserClick) {
|
||||
mRightPaneScrollView.setAccessibilityPaneTitle(getContext().getString(
|
||||
R.string.widget_picker_right_pane_accessibility_title,
|
||||
contentEntry.mPkgItem.title));
|
||||
postDelayed(() -> focusOnFirstWidgetCell(widgetsRowViewHolder.tableContainer),
|
||||
WIDGET_LIST_ITEM_APPEARANCE_DELAY);
|
||||
}
|
||||
mRightPaneScrollView.setScrollY(0);
|
||||
mRightPane.setAccessibilityPaneTitle(
|
||||
getContext().getString(
|
||||
R.string.widget_picker_right_pane_accessibility_title,
|
||||
contentEntry.mPkgItem.title));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -445,6 +483,18 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests focus on the first widget cell in the given widget section.
|
||||
*/
|
||||
private static void focusOnFirstWidgetCell(ViewGroup parent) {
|
||||
if (parent == null) return;
|
||||
WidgetCell cell = Utilities.findViewByPredicate(parent, v -> v instanceof WidgetCell);
|
||||
if (cell != null) {
|
||||
cell.performAccessibilityAction(
|
||||
AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void unselectWidgetCell(ViewGroup parent, WidgetItem item) {
|
||||
if (parent == null || item == null) return;
|
||||
WidgetCell cell = Utilities.findViewByPredicate(parent, v -> v instanceof WidgetCell wc
|
||||
|
||||
Reference in New Issue
Block a user