Snap for 12019843 from 1ec2aacc05 to 24Q4-release

Change-Id: I8a8760402e7605e29b416f7d6476a6d32bbf4003
This commit is contained in:
Android Build Coastguard Worker
2024-06-26 23:22:28 +00:00
32 changed files with 616 additions and 369 deletions
+7
View File
@@ -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.
*
@@ -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() =
@@ -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}"),
)
@@ -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
@@ -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
@@ -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