Snap for 11494200 from e98d88ef2f to 24Q2-release

Change-Id: I8de1fe57f6d93f1e04c6f15d16fe6d9fc083db35
This commit is contained in:
Android Build Coastguard Worker
2024-02-25 00:21:04 +00:00
18 changed files with 263 additions and 90 deletions
@@ -2049,7 +2049,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
private final RemoteAnimationTarget[] mAppTargets;
private final Matrix mMatrix = new Matrix();
private final Point mTmpPos = new Point();
private final Rect mCurrentRect = new Rect();
private final RectF mCurrentRectF = new RectF();
private final float mStartRadius;
private final float mEndRadius;
private final SurfaceTransactionApplier mSurfaceApplier;
@@ -2116,25 +2116,24 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
}
if (target.mode == MODE_CLOSING) {
transferRectToTargetCoordinate(target, currentRectF, false, currentRectF);
currentRectF.round(mCurrentRect);
transferRectToTargetCoordinate(target, currentRectF, false, mCurrentRectF);
// Scale the target window to match the currentRectF.
final float scale;
// We need to infer the crop (we crop the window to match the currentRectF).
if (mWindowStartBounds.height() > mWindowStartBounds.width()) {
scale = Math.min(1f, currentRectF.width() / mWindowOriginalBounds.width());
scale = Math.min(1f, mCurrentRectF.width() / mWindowOriginalBounds.width());
int unscaledHeight = (int) (mCurrentRect.height() * (1f / scale));
int unscaledHeight = (int) (mCurrentRectF.height() * (1f / scale));
int croppedHeight = mWindowStartBounds.height() - unscaledHeight;
mTmpRect.set(0, 0, mWindowOriginalBounds.width(),
mWindowStartBounds.height() - croppedHeight);
} else {
scale = Math.min(1f, currentRectF.height()
scale = Math.min(1f, mCurrentRectF.height()
/ mWindowOriginalBounds.height());
int unscaledWidth = (int) (mCurrentRect.width() * (1f / scale));
int unscaledWidth = (int) (mCurrentRectF.width() * (1f / scale));
int croppedWidth = mWindowStartBounds.width() - unscaledWidth;
mTmpRect.set(0, 0, mWindowStartBounds.width() - croppedWidth,
mWindowOriginalBounds.height());
@@ -2142,7 +2141,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
// Match size and position of currentRect.
mMatrix.setScale(scale, scale);
mMatrix.postTranslate(mCurrentRect.left, mCurrentRect.top);
mMatrix.postTranslate(mCurrentRectF.left, mCurrentRectF.top);
builder.setMatrix(mMatrix)
.setWindowCrop(mTmpRect)
@@ -219,7 +219,9 @@ public class WidgetPickerActivity extends BaseActivity {
final boolean isHorizontallyResizable =
(info.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
if (mDesiredWidgetWidth > 0 && isHorizontallyResizable) {
if (info.maxResizeWidth > 0 && info.maxResizeWidth < mDesiredWidgetWidth) {
if (info.maxResizeWidth > 0
&& info.maxResizeWidth >= info.minWidth
&& info.maxResizeWidth < mDesiredWidgetWidth) {
return rejectWidget(
widget,
"maxResizeWidth[%d] < mDesiredWidgetWidth[%d]",
@@ -227,12 +229,13 @@ public class WidgetPickerActivity extends BaseActivity {
mDesiredWidgetWidth);
}
final int minWidth = info.minResizeWidth > 0 ? info.minResizeWidth : info.minWidth;
final int minWidth = Math.min(info.minResizeWidth, info.minWidth);
if (minWidth > mDesiredWidgetWidth) {
return rejectWidget(
widget,
"minWidth[%d] > mDesiredWidgetWidth[%d]",
minWidth,
"min(minWidth[%d], minResizeWidth[%d]) > mDesiredWidgetWidth[%d]",
info.minWidth,
info.minResizeWidth,
mDesiredWidgetWidth);
}
}
@@ -240,7 +243,9 @@ public class WidgetPickerActivity extends BaseActivity {
final boolean isVerticallyResizable =
(info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
if (mDesiredWidgetHeight > 0 && isVerticallyResizable) {
if (info.maxResizeHeight > 0 && info.maxResizeHeight < mDesiredWidgetHeight) {
if (info.maxResizeHeight > 0
&& info.maxResizeHeight >= info.minHeight
&& info.maxResizeHeight < mDesiredWidgetHeight) {
return rejectWidget(
widget,
"maxResizeHeight[%d] < mDesiredWidgetHeight[%d]",
@@ -248,20 +253,19 @@ public class WidgetPickerActivity extends BaseActivity {
mDesiredWidgetHeight);
}
final int minHeight = info.minResizeHeight > 0 ? info.minResizeHeight : info.minHeight;
final int minHeight = Math.min(info.minResizeHeight, info.minHeight);
if (minHeight > mDesiredWidgetHeight) {
return rejectWidget(
widget,
"minHeight[%d] > mDesiredWidgetHeight[%d]",
minHeight,
"min(minHeight[%d], minResizeHeight[%d]) > mDesiredWidgetHeight[%d]",
info.minHeight,
info.minResizeHeight,
mDesiredWidgetHeight);
}
}
if (!isHorizontallyResizable
&& !isVerticallyResizable
&& (info.minWidth < mDesiredWidgetWidth || info.minHeight < mDesiredWidgetHeight)) {
return rejectWidget(widget, "too small and not resizeable");
if (!isHorizontallyResizable || !isVerticallyResizable) {
return rejectWidget(widget, "not resizeable");
}
return acceptWidget(widget);
@@ -271,12 +275,15 @@ public class WidgetPickerActivity extends BaseActivity {
WidgetItem widget, String rejectionReason, Object... args) {
return new WidgetAcceptabilityVerdict(
false,
widget.label,
widget.widgetInfo != null
? widget.widgetInfo.provider.flattenToShortString()
: widget.label,
String.format(Locale.ENGLISH, rejectionReason, args));
}
private static WidgetAcceptabilityVerdict acceptWidget(WidgetItem widget) {
return new WidgetAcceptabilityVerdict(true, widget.label, "");
return new WidgetAcceptabilityVerdict(
true, widget.widgetInfo.provider.flattenToShortString(), "");
}
private record WidgetAcceptabilityVerdict(
@@ -16,7 +16,10 @@
package com.android.launcher3.taskbar;
import androidx.annotation.Nullable;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.quickstep.util.TISBindHelper;
/**
* A data source which integrates with a Launcher instance, used specifically for a
@@ -50,4 +53,10 @@ public class DesktopTaskbarUIController extends TaskbarUIController {
public boolean supportsVisualStashing() {
return false;
}
@Nullable
@Override
protected TISBindHelper getTISBindHelper() {
return mLauncher.getTISBindHelper();
}
}
@@ -21,11 +21,14 @@ import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASH
import android.animation.Animator;
import androidx.annotation.Nullable;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.statemanager.StateManager;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.RecentsView;
import java.util.stream.Stream;
@@ -124,4 +127,10 @@ public class FallbackTaskbarUIController extends TaskbarUIController {
.get(mControllers.taskbarActivityContext).getCachedTopTask(true);
return topTask.isHomeTask() || topTask.isRecentsTask();
}
@Nullable
@Override
protected TISBindHelper getTISBindHelper() {
return mRecentsActivity.getTISBindHelper();
}
}
@@ -147,7 +147,7 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
KeyboardQuickSwitchTaskView taskView = (KeyboardQuickSwitchTaskView) layoutInflater.inflate(
R.layout.keyboard_quick_switch_taskview, mContent, false);
taskView.setId(View.generateViewId());
taskView.setOnClickListener(v -> mViewCallbacks.launchTappedTask(index));
taskView.setOnClickListener(v -> mViewCallbacks.launchTaskAt(index));
LayoutParams lp = new LayoutParams(width, mTaskViewHeight);
// Create a left-to-right ordering of views (or right-to-left in RTL locales)
@@ -186,7 +186,7 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
KeyboardQuickSwitchTaskView overviewButton =
(KeyboardQuickSwitchTaskView) layoutInflater.inflate(
R.layout.keyboard_quick_switch_overview, this, false);
overviewButton.setOnClickListener(v -> mViewCallbacks.launchTappedTask(MAX_TASKS));
overviewButton.setOnClickListener(v -> mViewCallbacks.launchTaskAt(MAX_TASKS));
overviewButton.<TextView>findViewById(R.id.text).setText(overflowString);
@@ -22,7 +22,6 @@ import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.animation.Animator;
import android.app.ActivityOptions;
import android.view.KeyEvent;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.window.RemoteTransition;
@@ -137,7 +136,6 @@ public class KeyboardQuickSwitchViewController {
}
// Even with a valid index, this can be null if the user tries to quick switch before the
// views have been added in the KeyboardQuickSwitchView.
View taskView = mKeyboardQuickSwitchView.getTaskAt(index);
GroupTask task = mControllerCallbacks.getTaskAt(index);
if (task == null) {
return Math.max(0, index);
@@ -198,13 +196,18 @@ public class KeyboardQuickSwitchViewController {
&& keyCode != KeyEvent.KEYCODE_DPAD_RIGHT
&& keyCode != KeyEvent.KEYCODE_DPAD_LEFT
&& keyCode != KeyEvent.KEYCODE_GRAVE
&& keyCode != KeyEvent.KEYCODE_ESCAPE) {
&& keyCode != KeyEvent.KEYCODE_ESCAPE
&& keyCode != KeyEvent.KEYCODE_ENTER) {
return false;
}
if (keyCode == KeyEvent.KEYCODE_GRAVE || keyCode == KeyEvent.KEYCODE_ESCAPE) {
closeQuickSwitchView(true);
return true;
}
if (keyCode == KeyEvent.KEYCODE_ENTER) {
launchTaskAt(mCurrentFocusIndex);
return true;
}
if (!allowTraversal) {
return false;
}
@@ -234,9 +237,10 @@ public class KeyboardQuickSwitchViewController {
mCurrentFocusIndex = index;
}
void launchTappedTask(int index) {
KeyboardQuickSwitchViewController.this.launchTaskAt(index);
closeQuickSwitchView(true);
void launchTaskAt(int index) {
mCurrentFocusIndex = Utilities.boundToRange(
index, 0, mKeyboardQuickSwitchView.getChildCount() - 1);
mControllers.taskbarActivityContext.launchKeyboardFocusedTask();
}
void updateThumbnailInBackground(Task task, Consumer<ThumbnailData> callback) {
@@ -52,6 +52,7 @@ import com.android.launcher3.util.OnboardingPrefs;
import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.RecentsView;
import java.io.PrintWriter;
@@ -428,6 +429,12 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
mTaskbarLauncherStateController.resetIconAlignment();
}
@Nullable
@Override
protected TISBindHelper getTISBindHelper() {
return mLauncher.getTISBindHelper();
}
@Override
public void dumpLogs(String prefix, PrintWriter pw) {
super.dumpLogs(prefix, pw);
@@ -505,52 +505,26 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
/**
* Creates {@link WindowManager.LayoutParams} for Taskbar, and also sets LP.paramsForRotation
* for taskbar showing as navigation bar
* for taskbar
*/
private WindowManager.LayoutParams createAllWindowParams() {
final int windowType =
ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL;
WindowManager.LayoutParams windowLayoutParams =
createDefaultWindowLayoutParams(windowType, TaskbarActivityContext.WINDOW_TITLE);
if (!isPhoneButtonNavMode()) {
return windowLayoutParams;
}
// Provide WM layout params for all rotations to cache, see NavigationBar#getBarLayoutParams
int width = WindowManager.LayoutParams.MATCH_PARENT;
int height = WindowManager.LayoutParams.MATCH_PARENT;
int gravity = Gravity.BOTTOM;
windowLayoutParams.paramsForRotation = new WindowManager.LayoutParams[4];
for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
WindowManager.LayoutParams lp =
createDefaultWindowLayoutParams(windowType,
TaskbarActivityContext.WINDOW_TITLE);
switch (rot) {
case Surface.ROTATION_0, Surface.ROTATION_180 -> {
// Defaults are fine
width = WindowManager.LayoutParams.MATCH_PARENT;
height = mLastRequestedNonFullscreenSize;
gravity = Gravity.BOTTOM;
}
case Surface.ROTATION_90 -> {
width = mLastRequestedNonFullscreenSize;
height = WindowManager.LayoutParams.MATCH_PARENT;
gravity = Gravity.END;
}
case Surface.ROTATION_270 -> {
width = mLastRequestedNonFullscreenSize;
height = WindowManager.LayoutParams.MATCH_PARENT;
gravity = Gravity.START;
}
if (isPhoneButtonNavMode()) {
populatePhoneButtonNavModeWindowLayoutParams(rot, lp);
}
lp.width = width;
lp.height = height;
lp.gravity = gravity;
windowLayoutParams.paramsForRotation[rot] = lp;
}
// Override current layout params
// Override with current layout params
WindowManager.LayoutParams currentParams =
windowLayoutParams.paramsForRotation[getDisplay().getRotation()];
windowLayoutParams.width = currentParams.width;
@@ -560,6 +534,32 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
return windowLayoutParams;
}
/**
* Update {@link WindowManager.LayoutParams} with values specific to phone and 3 button
* navigation users
*/
private void populatePhoneButtonNavModeWindowLayoutParams(int rot,
WindowManager.LayoutParams lp) {
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
lp.height = WindowManager.LayoutParams.MATCH_PARENT;
lp.gravity = Gravity.BOTTOM;
// Override with per-rotation specific values
switch (rot) {
case Surface.ROTATION_0, Surface.ROTATION_180 -> {
lp.height = mLastRequestedNonFullscreenSize;
}
case Surface.ROTATION_90 -> {
lp.width = mLastRequestedNonFullscreenSize;
lp.gravity = Gravity.END;
}
case Surface.ROTATION_270 -> {
lp.width = mLastRequestedNonFullscreenSize;
lp.gravity = Gravity.START;
}
}
}
public void onConfigurationChanged(@Config int configChanges) {
mControllers.onConfigurationChanged(configChanges);
if (!mIsUserSetupComplete) {
@@ -920,8 +920,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
}
if (landscapePhoneButtonNav) {
mWindowLayoutParams.width = size;
mWindowLayoutParams.paramsForRotation[getDisplay().getRotation()].width = size;
} else {
mWindowLayoutParams.height = size;
mWindowLayoutParams.paramsForRotation[getDisplay().getRotation()].height = size;
}
mControllers.taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
notifyUpdateLayoutParams();
@@ -1485,6 +1487,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
btv.post(() -> mControllers.taskbarPopupController.showForIcon(btv));
}
public void launchKeyboardFocusedTask() {
mControllers.uiController.launchKeyboardFocusedTask();
}
public boolean isInApp() {
return mControllers.taskbarStashController.isInApp();
}
@@ -118,11 +118,9 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
getProvidedInsets(insetsRoundedCornerFlag)
}
if (!context.isGestureNav) {
if (windowLayoutParams.paramsForRotation != null) {
for (layoutParams in windowLayoutParams.paramsForRotation) {
layoutParams.providedInsets = getProvidedInsets(insetsRoundedCornerFlag)
}
if (windowLayoutParams.paramsForRotation != null) {
for (layoutParams in windowLayoutParams.paramsForRotation) {
layoutParams.providedInsets = getProvidedInsets(insetsRoundedCornerFlag)
}
}
@@ -156,19 +154,12 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
)
}
val gravity = windowLayoutParams.gravity
// Pre-calculate insets for different providers across different rotations for this gravity
for (rotation in Surface.ROTATION_0..Surface.ROTATION_270) {
// Add insets for navbar rotated params
if (windowLayoutParams.paramsForRotation != null) {
val layoutParams = windowLayoutParams.paramsForRotation[rotation]
for (provider in layoutParams.providedInsets) {
setProviderInsets(provider, layoutParams.gravity, rotation)
}
}
for (provider in windowLayoutParams.providedInsets) {
setProviderInsets(provider, gravity, rotation)
val layoutParams = windowLayoutParams.paramsForRotation[rotation]
for (provider in layoutParams.providedInsets) {
setProviderInsets(provider, layoutParams.gravity, rotation)
}
}
context.notifyUpdateLayoutParams()
@@ -20,6 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
import static com.android.quickstep.OverviewCommandHelper.TYPE_HIDE;
import android.content.Intent;
import android.graphics.drawable.BitmapDrawable;
@@ -38,7 +39,9 @@ import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
@@ -361,6 +364,28 @@ public class TaskbarUIController {
/** Adjusts the hotseat for the bubble bar. */
public void adjustHotseatForBubbleBar(boolean isBubbleBarVisible) {}
@Nullable
protected TISBindHelper getTISBindHelper() {
return null;
}
/**
* Launches the focused task in the Keyboard Quick Switch view through the OverviewCommandHelper
* <p>
* Use this helper method when the focused task may be the overview task.
*/
public void launchKeyboardFocusedTask() {
TISBindHelper tisBindHelper = getTISBindHelper();
if (tisBindHelper == null) {
return;
}
OverviewCommandHelper overviewCommandHelper = tisBindHelper.getOverviewCommandHelper();
if (overviewCommandHelper == null) {
return;
}
overviewCommandHelper.addCommand(TYPE_HIDE);
}
/**
* Adjusts the taskbar based on the visibility of the launcher.
* @param isVisible True if launcher is visible, false otherwise.
@@ -650,10 +650,13 @@ public class QuickstepLauncher extends Launcher {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Back dispatcher is registered in {@link BaseActivity#onCreate}. For predictive back to
// work, we must opt-in BEFORE registering back dispatcher. So we need to call
// setEnableOnBackInvokedCallback() before super.onCreate()
if (Utilities.ATLEAST_U && enablePredictiveBackGesture()) {
getApplicationInfo().setEnableOnBackInvokedCallback(true);
}
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mPendingSplitSelectInfo = ObjectWrapper.unwrap(
savedInstanceState.getIBinder(PENDING_SPLIT_SELECT_INFO));
@@ -1360,6 +1363,11 @@ public class QuickstepLauncher extends Launcher {
return (mTaskbarUIController != null && mTaskbarUIController.hasBubbles());
}
@NonNull
public TISBindHelper getTISBindHelper() {
return mTISBindHelper;
}
@Override
public boolean handleIncorrectSplitTargetSelection() {
if (!enableSplitContextually() || !mSplitSelectStateController.isSplitSelectActive()) {
@@ -324,12 +324,12 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
@Override
public void onDragEnd(PointF velocity) {
cancelAnimations();
boolean horizontalFling = mSwipeDetector.isFling(velocity.x);
boolean verticalFling = mSwipeDetector.isFling(velocity.y);
boolean noFling = !horizontalFling && !verticalFling;
if (mMotionPauseDetector.isPaused() && noFling) {
// Going to Overview.
cancelAnimations();
InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
StateAnimationConfig config = new StateAnimationConfig();
@@ -455,7 +455,6 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState));
cancelAnimations();
xOverviewAnim.start();
yOverviewAnim.start();
nonOverviewAnim.start();
@@ -52,6 +52,7 @@ import android.window.BackMotionEvent;
import android.window.BackProgressAnimator;
import android.window.IOnBackInvokedCallback;
import com.android.app.animation.Interpolators;
import com.android.internal.view.AppearanceRegion;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
@@ -103,7 +104,8 @@ public class LauncherBackAnimationController {
private float mWindowScaleEndCornerRadius;
private float mWindowScaleStartCornerRadius;
private final Interpolator mCancelInterpolator;
private final Interpolator mProgressInterpolator = new DecelerateInterpolator();
private final Interpolator mProgressInterpolator = Interpolators.STANDARD_DECELERATE;
private final Interpolator mVerticalMoveInterpolator = new DecelerateInterpolator();
private final PointF mInitialTouchPos = new PointF();
private RemoteAnimationTarget mBackTarget;
@@ -376,7 +378,7 @@ public class LauncherBackAnimationController {
float yDirection = rawYDelta < 0 ? -1 : 1;
// limit yDelta interpretation to 1/2 of screen height in either direction
float deltaYRatio = Math.min(screenHeight / 2f, Math.abs(rawYDelta)) / (screenHeight / 2f);
float interpolatedYRatio = mProgressInterpolator.getInterpolation(deltaYRatio);
float interpolatedYRatio = mVerticalMoveInterpolator.getInterpolation(deltaYRatio);
// limit y-shift so surface never passes 8dp screen margin
float deltaY = yDirection * interpolatedYRatio * Math.max(0f, (screenHeight - height)
/ 2f - mWindowScaleMarginX);
@@ -498,4 +498,9 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> {
OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper();
return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely();
}
@NonNull
public TISBindHelper getTISBindHelper() {
return mTISBindHelper;
}
}
@@ -20,6 +20,7 @@ package com.android.quickstep.util;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_LAUNCH;
import static com.android.launcher3.model.data.AppInfo.PACKAGE_KEY_COMPARATOR;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -30,6 +31,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.isPersisten
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.LauncherApps;
import android.util.Log;
import android.util.Pair;
@@ -42,10 +44,12 @@ import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -93,14 +97,38 @@ public class AppPairsController {
}
/**
* Creates a new app pair ItemInfo and adds it to the workspace
* Creates a new app pair ItemInfo and adds it to the workspace.
* <br>
* We create WorkspaceItemInfos to save onto the app pair in the following way:
* <br> 1. We verify that the ComponentKey from our Recents tile corresponds to a real
* launchable app in the app store.
* <br> 2. If it doesn't, we search for the underlying launchable app via package name, and use
* that instead.
* <br> 3. If that fails, we re-use the existing WorkspaceItemInfo by cloning it and replacing
* its intent with one from PackageManager.
* <br> 4. If everything fails, we just use the WorkspaceItemInfo as is, with its existing
* intent. This is not preferred, but will still work in most cases (notably it will not work
* well on trampoline apps).
*/
public void saveAppPair(GroupedTaskView gtv) {
TaskView.TaskIdAttributeContainer[] attributes = gtv.getTaskIdAttributeContainers();
WorkspaceItemInfo app1 = attributes[0].getItemInfo().clone();
WorkspaceItemInfo app2 = attributes[1].getItemInfo().clone();
app1.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
app2.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
WorkspaceItemInfo recentsInfo1 = attributes[0].getItemInfo();
WorkspaceItemInfo recentsInfo2 = attributes[0].getItemInfo();
WorkspaceItemInfo app1 = lookupLaunchableItem(recentsInfo1.getComponentKey());
WorkspaceItemInfo app2 = lookupLaunchableItem(recentsInfo2.getComponentKey());
// If app lookup fails, use the WorkspaceItemInfo that we have, but try to override default
// intent with one from PackageManager.
if (app1 == null) {
Log.w(TAG, "Creating an app pair, but app lookup for " + recentsInfo1.title
+ " failed. Falling back to the WorkspaceItemInfo from Recents.");
app1 = convertRecentsItemToAppItem(recentsInfo1);
}
if (app2 == null) {
Log.w(TAG, "Creating an app pair, but app lookup for " + recentsInfo2.title
+ " failed. Falling back to the WorkspaceItemInfo from Recents.");
app2 = convertRecentsItemToAppItem(recentsInfo2);
}
@PersistentSnapPosition int snapPosition = gtv.getSnapPosition();
if (!isPersistentSnapPosition(snapPosition)) {
@@ -188,6 +216,52 @@ public class AppPairsController {
);
}
/**
* Creates a new launchable WorkspaceItemInfo of itemType=ITEM_TYPE_APPLICATION by looking the
* ComponentKey up in the AllAppsStore. If no app is found, attempts a lookup by package
* instead. If that lookup fails, returns null.
*/
@Nullable
private WorkspaceItemInfo lookupLaunchableItem(@Nullable ComponentKey key) {
if (key == null) {
return null;
}
AllAppsStore appsStore = Launcher.getLauncher(mContext).getAppsView().getAppsStore();
// Lookup by ComponentKey
AppInfo appInfo = appsStore.getApp(key);
if (appInfo == null) {
// Lookup by package
appInfo = appsStore.getApp(key, PACKAGE_KEY_COMPARATOR);
}
return appInfo != null ? appInfo.makeWorkspaceItem(mContext) : null;
}
/**
* Converts a WorkspaceItemInfo of itemType=ITEM_TYPE_TASK (from a Recents task) to a new
* WorkspaceItemInfo of itemType=ITEM_TYPE_APPLICATION.
*/
private WorkspaceItemInfo convertRecentsItemToAppItem(WorkspaceItemInfo recentsItem) {
if (recentsItem.itemType != LauncherSettings.Favorites.ITEM_TYPE_TASK) {
Log.w(TAG, "Expected ItemInfo of type ITEM_TYPE_TASK, but received "
+ recentsItem.itemType);
}
WorkspaceItemInfo launchableItem = recentsItem.clone();
PackageManager p = mContext.getPackageManager();
Intent launchIntent = p.getLaunchIntentForPackage(recentsItem.getTargetPackage());
Log.w(TAG, "Initial intent from Recents: " + launchableItem.intent + "\n"
+ "Intent from PackageManager: " + launchIntent);
if (launchIntent != null) {
// If lookup from PackageManager fails, just use the existing intent
launchableItem.intent = launchIntent;
}
launchableItem.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
return launchableItem;
}
/**
* Handles the complicated logic for how to animate an app pair entrance when already inside an
* app or app pair.
@@ -40,6 +40,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -138,12 +139,22 @@ public class AllAppsStore<T extends Context & ActivityContext> {
/**
* Returns {@link AppInfo} if any apps matches with provided {@link ComponentKey}, otherwise
* null.
*
* Uses {@link AppInfo#COMPONENT_KEY_COMPARATOR} as a default comparator.
*/
@Nullable
public AppInfo getApp(ComponentKey key) {
return getApp(key, COMPONENT_KEY_COMPARATOR);
}
/**
* Generic version of {@link #getApp(ComponentKey)} that allows comparator to be specified.
*/
@Nullable
public AppInfo getApp(ComponentKey key, Comparator<AppInfo> comparator) {
mTempInfo.componentName = key.componentName;
mTempInfo.user = key.user;
int index = Arrays.binarySearch(mApps, mTempInfo, COMPONENT_KEY_COMPARATOR);
int index = Arrays.binarySearch(mApps, mTempInfo, comparator);
return index < 0 ? null : mApps[index];
}
@@ -52,6 +52,9 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
return uc != 0 ? uc : a.componentName.compareTo(b.componentName);
};
public static final Comparator<AppInfo> PACKAGE_KEY_COMPARATOR = Comparator.comparingInt(
(AppInfo a) -> a.user.hashCode()).thenComparing(ItemInfo::getTargetPackage);
/**
* The intent used to start the application.
*/
@@ -34,6 +34,17 @@ import com.android.launcher3.util.Executors;
*/
public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHostView {
private static final ViewOutlineProvider VIEW_OUTLINE_PROVIDER = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
// Since ShortcutAndWidgetContainer sets clipChildren to false, we should restrict the
// outline to be the view bounds, otherwise widgets might draw themselves outside of
// the launcher view. Setting alpha to 0 to match the previous behavior.
outline.setRect(0, 0, view.getWidth(), view.getHeight());
outline.setAlpha(.0f);
}
};
protected final LayoutInflater mInflater;
private final Rect mEnforcedRectangle = new Rect();
@@ -49,10 +60,13 @@ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHo
}
};
private boolean mIsCornerRadiusEnforced;
public BaseLauncherAppWidgetHostView(Context context) {
super(context);
setExecutor(Executors.THREAD_POOL_EXECUTOR);
setClipToOutline(true);
mInflater = LayoutInflater.from(context);
mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext());
@@ -84,8 +98,8 @@ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHo
@UiThread
private void resetRoundedCorners() {
setOutlineProvider(ViewOutlineProvider.BACKGROUND);
setClipToOutline(false);
setOutlineProvider(VIEW_OUTLINE_PROVIDER);
mIsCornerRadiusEnforced = false;
}
@UiThread
@@ -104,7 +118,7 @@ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHo
background,
mEnforcedRectangle);
setOutlineProvider(mCornerRadiusEnforcementOutline);
setClipToOutline(true);
mIsCornerRadiusEnforced = true;
invalidateOutline();
}
@@ -115,6 +129,6 @@ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHo
/** Returns true if the corner radius are enforced for this App Widget. */
public boolean hasEnforcedCornerRadius() {
return getClipToOutline();
return mIsCornerRadiusEnforced;
}
}