diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java index 5819bb31cb..6dc7db766e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java @@ -71,6 +71,7 @@ import com.android.launcher3.taskbar.TaskbarControllers; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.Executors.SimpleThreadFactory; import com.android.quickstep.SystemUiProxy; +import com.android.wm.shell.Flags; import com.android.wm.shell.bubbles.IBubblesListener; import com.android.wm.shell.common.bubbles.BubbleBarUpdate; import com.android.wm.shell.common.bubbles.BubbleInfo; @@ -101,8 +102,8 @@ public class BubbleBarController extends IBubblesListener.Stub { * * @see #onTaskbarRecreated() */ - private static boolean sBubbleBarEnabled = - SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false); + private static boolean sBubbleBarEnabled = Flags.enableBubbleBar() + || SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false); /** Whether showing bubbles in the launcher bubble bar is enabled. */ public static boolean isBubbleBarEnabled() { @@ -111,8 +112,10 @@ public class BubbleBarController extends IBubblesListener.Stub { /** Re-reads the value of the flag from SystemProperties when taskbar is recreated. */ public static void onTaskbarRecreated() { - sBubbleBarEnabled = SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false); + sBubbleBarEnabled = Flags.enableBubbleBar() + || SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false); } + private static final int MASK_HIDE_BUBBLE_BAR = SYSUI_STATE_BOUNCER_SHOWING | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java index 22f24f185a..039c0a0ad8 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java @@ -28,10 +28,8 @@ import android.util.Log; import android.util.Pair; import android.view.View; import android.widget.RemoteViews; -import android.widget.Toast; import android.window.SplashScreen; -import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.ItemInfo; @@ -63,8 +61,7 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler { // Log metric StatsLogManager.StatsLogger logger = mLauncher.getStatsLogManager().logger(); logger.log(LAUNCHER_SPLIT_WIDGET_ATTEMPT); - Toast.makeText(hostView.getContext(), R.string.split_widgets_not_supported, - Toast.LENGTH_SHORT).show(); + mLauncher.handleIncorrectSplitTargetSelection(); return true; } Pair options = remoteResponse.getLaunchOptions(view); diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 32d10b04ef..ac0e53e3d2 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -36,6 +36,7 @@ import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK; import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT; import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; +import static com.android.launcher3.config.FeatureFlags.enableSplitContextually; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP; import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition; @@ -676,7 +677,7 @@ public class QuickstepLauncher extends Launcher { splitSelectSource.alreadyRunningTaskId = taskWasFound ? foundTask.key.id : INVALID_TASK_ID; - if (FeatureFlags.enableSplitContextually()) { + if (enableSplitContextually()) { startSplitToHome(splitSelectSource); } else { recentsView.initiateSplitSelect(splitSelectSource); @@ -761,7 +762,7 @@ public class QuickstepLauncher extends Launcher { super.onPause(); - if (FeatureFlags.enableSplitContextually()) { + if (enableSplitContextually()) { // If Launcher pauses before both split apps are selected, exit split screen. if (!mSplitSelectStateController.isBothSplitAppsConfirmed() && !mSplitSelectStateController.isLaunchingFirstAppFullscreen()) { @@ -1352,6 +1353,15 @@ public class QuickstepLauncher extends Launcher { return (mTaskbarUIController != null && mTaskbarUIController.hasBubbles()); } + @Override + public boolean handleIncorrectSplitTargetSelection() { + if (enableSplitContextually() && !mSplitSelectStateController.isSplitSelectActive()) { + return false; + } + mSplitSelectStateController.getSplitInstructionsView().goBoing(); + return true; + } + private static final class LauncherTaskViewController extends TaskViewTouchController { diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java index e77a4504e5..b42eb067e4 100644 --- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java +++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java @@ -371,8 +371,8 @@ public class FallbackSwipeHandler extends if (mSpringAnim != null) { mSpringAnim.onTargetPositionChanged(); } - mOnFinishCallback = data.getParcelable(EXTRA_ON_FINISH_CALLBACK); } + mOnFinishCallback = data.getParcelable(EXTRA_ON_FINISH_CALLBACK); maybeSendEndMessage(); } catch (Exception e) { // Ignore diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 3735cbf1d5..522842095f 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -24,6 +24,7 @@ import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.Flags.enableCursorHoverStates; +import static com.android.launcher3.Flags.useActivityOverlay; import static com.android.launcher3.Launcher.INTENT_ACTION_ALL_APPS_TOGGLE; import static com.android.launcher3.LauncherPrefs.backedUpItem; import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent; @@ -1159,6 +1160,14 @@ public class TouchInteractionService extends Service { boolean launcherResumedThroughShellTransition = gestureState.getActivityInterface().isResumed() && !previousGestureState.isRecentsAnimationRunning(); + // If a task fragment within Launcher is resumed + boolean launcherChildActivityResumed = useActivityOverlay() + && runningTask != null + && runningTask.isHomeTask() + && mOverviewComponentObserver.isHomeAndOverviewSame() + && !launcherResumedThroughShellTransition + && !previousGestureState.isRecentsAnimationRunning(); + if (gestureState.getActivityInterface().isInLiveTileMode()) { return createOverviewInputConsumer( previousGestureState, @@ -1185,9 +1194,11 @@ public class TouchInteractionService extends Service { ? "launcher resumed through a shell transition" : "forceOverviewInputConsumer == true")) .append(", trying to use overview input consumer")); - } else if (mDeviceState.isGestureBlockedTask(runningTask)) { + } else if (mDeviceState.isGestureBlockedTask(runningTask) || launcherChildActivityResumed) { return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX) - .append("is gesture-blocked task, trying to use default input consumer")); + .append(launcherChildActivityResumed + ? "is launcher child-task, trying to use default input consumer" + : "is gesture-blocked task, trying to use default input consumer")); } else { reasonString.append(SUBSTRING_PREFIX) .append("using OtherActivityInputConsumer"); diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.java b/quickstep/src/com/android/quickstep/views/IconAppChipView.java index d2b0540702..0a261ef095 100644 --- a/quickstep/src/com/android/quickstep/views/IconAppChipView.java +++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.java @@ -86,6 +86,7 @@ public class IconAppChipView extends FrameLayout implements TaskViewIcon { private final int mMinIconBackgroundHeight; private final int mMaxIconBackgroundCornerRadius; private final float mMinIconBackgroundCornerRadius; + private AnimatorSet mAnimator; private int mMaxWidth = Integer.MAX_VALUE; @@ -316,11 +317,13 @@ public class IconAppChipView extends FrameLayout implements TaskViewIcon { } protected void revealAnim(boolean isRevealing) { + cancelInProgressAnimations(); + if (isRevealing) { boolean isRtl = isLayoutRtl(); bringToFront(); ((AnimatedVectorDrawable) mIconArrowView.getDrawable()).start(); - AnimatorSet anim = new AnimatorSet(); + mAnimator = new AnimatorSet(); float backgroundScaleY = mMaxIconBackgroundHeight / (float) mMinIconBackgroundHeight; float maxCornerSize = Math.min(mMaxIconBackgroundHeight / 2f, mMaxIconBackgroundCornerRadius); @@ -341,7 +344,7 @@ public class IconAppChipView extends FrameLayout implements TaskViewIcon { mIconTextMaxWidth + maxCornerSize); } }); - anim.playTogether( + mAnimator.playTogether( expandedTextRevealAnim, ObjectAnimator.ofFloat(mIconViewBackgroundCornersStart, SCALE_Y, backgroundScaleY), @@ -367,9 +370,9 @@ public class IconAppChipView extends FrameLayout implements TaskViewIcon { ObjectAnimator.ofFloat(mIconTextExpandedView, ALPHA, 1), ObjectAnimator.ofFloat(mIconArrowView, TRANSLATION_X, isRtl ? -arrowTranslationX : arrowTranslationX)); - anim.setDuration(MENU_BACKGROUND_REVEAL_DURATION); - anim.setInterpolator(EMPHASIZED); - anim.start(); + mAnimator.setDuration(MENU_BACKGROUND_REVEAL_DURATION); + mAnimator.setInterpolator(EMPHASIZED); + mAnimator.start(); } else { ((AnimatedVectorDrawable) mIconArrowView.getDrawable()).reverse(); float maxCornerSize = Math.min(mMaxIconBackgroundHeight / 2f, @@ -386,8 +389,8 @@ public class IconAppChipView extends FrameLayout implements TaskViewIcon { mIconTextExpandedView.getHeight() / 2f, 0); } }); - AnimatorSet anim = new AnimatorSet(); - anim.playTogether( + mAnimator = new AnimatorSet(); + mAnimator.playTogether( expandedTextClipAnim, ObjectAnimator.ofFloat(mIconViewBackgroundCornersStart, SCALE_X, 1), ObjectAnimator.ofFloat(mIconViewBackgroundCornersStart, SCALE_Y, 1), @@ -403,9 +406,9 @@ public class IconAppChipView extends FrameLayout implements TaskViewIcon { ObjectAnimator.ofFloat(mIconTextCollapsedView, ALPHA, 1), ObjectAnimator.ofFloat(mIconTextExpandedView, ALPHA, 0), ObjectAnimator.ofFloat(mIconArrowView, TRANSLATION_X, 0)); - anim.setDuration(MENU_BACKGROUND_HIDE_DURATION); - anim.setInterpolator(EMPHASIZED); - anim.start(); + mAnimator.setDuration(MENU_BACKGROUND_HIDE_DURATION); + mAnimator.setInterpolator(EMPHASIZED); + mAnimator.start(); } } @@ -425,6 +428,16 @@ public class IconAppChipView extends FrameLayout implements TaskViewIcon { mIconTextExpandedView.setAlpha(0); mIconArrowView.setTranslationX(0); ((AnimatedVectorDrawable) mIconArrowView.getDrawable()).reset(); + mAnimator = null; + } + + private void cancelInProgressAnimations() { + // We null the `AnimatorSet` because it holds references to the `Animators` which aren't + // expecting to be mutable and will cause a crash if they are re-used. + if (mAnimator != null && mAnimator.isStarted()) { + mAnimator.cancel(); + mAnimator = null; + } } @Override diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java index d7792765a6..cf508350d3 100644 --- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java +++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java @@ -24,6 +24,7 @@ import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Outline; import android.graphics.Rect; @@ -69,6 +70,8 @@ public class TaskMenuView extends AbstractFloatingView { private TextView mTaskName; @Nullable private AnimatorSet mOpenCloseAnimator; + @Nullable + private ValueAnimator mRevealAnimator; @Nullable private Runnable mOnClosingStartCallback; private TaskView mTaskView; private TaskIdAttributeContainer mTaskContainer; @@ -290,13 +293,18 @@ public class TaskMenuView extends AbstractFloatingView { private void animateOpenOrClosed(boolean closing) { if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) { - mOpenCloseAnimator.end(); + mOpenCloseAnimator.cancel(); } mOpenCloseAnimator = new AnimatorSet(); - - final Animator revealAnimator = createOpenCloseOutlineProvider() - .createRevealAnimator(this, closing); - revealAnimator.setInterpolator(enableOverviewIconMenu() ? Interpolators.EMPHASIZED + // If we're opening, we just start from the beginning as a new `TaskMenuView` is created + // each time we do the open animation so there will never be a partial value here. + float revealAnimationStartProgress = 0f; + if (closing && mRevealAnimator != null) { + revealAnimationStartProgress = 1f - mRevealAnimator.getAnimatedFraction(); + } + mRevealAnimator = createOpenCloseOutlineProvider() + .createRevealAnimator(this, closing, revealAnimationStartProgress); + mRevealAnimator.setInterpolator(enableOverviewIconMenu() ? Interpolators.EMPHASIZED : Interpolators.DECELERATE); if (enableOverviewIconMenu()) { @@ -349,7 +357,7 @@ public class TaskMenuView extends AbstractFloatingView { mOpenCloseAnimator.playTogether(translationXAnim, menuTranslationXAnim); } - mOpenCloseAnimator.playTogether(revealAnimator, + mOpenCloseAnimator.playTogether(mRevealAnimator, ObjectAnimator.ofFloat( mTaskContainer.getThumbnailView(), DIM_ALPHA, closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA), @@ -378,6 +386,7 @@ public class TaskMenuView extends AbstractFloatingView { mIsOpen = false; resetOverviewIconMenu(); mActivity.getDragLayer().removeView(this); + mRevealAnimator = null; } private void resetOverviewIconMenu() { diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index 11e721e18e..b971f67517 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -1137,9 +1137,8 @@ public class TaskView extends FrameLayout implements Reusable { DeviceProfile dp = mActivity.getDeviceProfile(); if (enableOverviewIconMenu() && iconView instanceof IconAppChipView) { ((IconAppChipView) iconView).revealAnim(/* isRevealing= */ true); - return TaskMenuView.showForTask(menuContainer, () -> { - ((IconAppChipView) iconView).revealAnim(/* isRevealing= */ false); - }); + return TaskMenuView.showForTask(menuContainer, + () -> ((IconAppChipView) iconView).revealAnim(/* isRevealing= */ false)); } else if (dp.isTablet) { int alignedOptionIndex = 0; if (getRecentsView().isOnGridBottomRow(menuContainer.getTaskView()) && dp.isLandscape) { diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 3016559f96..3aa4a77431 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -56,7 +56,7 @@ 13dp 24dp - 22dp + 23dp 24dp @@ -196,6 +196,8 @@ 20dp 2dp 16dp + + 0dp 24dp 0.5dp diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java index 4427a49dab..f9d047b980 100644 --- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java +++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java @@ -20,6 +20,7 @@ import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.TextWatcher; import android.text.style.SuggestionSpan; +import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.View.OnFocusChangeListener; @@ -42,6 +43,7 @@ public class AllAppsSearchBarController implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener, OnFocusChangeListener { + private static final String TAG = "AllAppsSearchBarController"; protected ActivityContext mLauncher; protected SearchCallback mCallback; protected ExtendedEditText mInput; @@ -122,6 +124,7 @@ public class AllAppsSearchBarController public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO) { + Log.i(TAG, "User tapped ime search button"); // selectFocusedView should return SearchTargetEvent that is passed onto onClick return mLauncher.getAppsView().getMainAdapterProvider().launchHighlightedItem(); } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 7ae70e097f..2f3f029963 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -800,6 +800,14 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return; } + int size = getIconsInReadingOrder().size(); + if (size <= 1) { + Log.d(TAG, "Couldn't animate folder closed because there's " + size + " icons"); + closeComplete(false); + post(this::announceAccessibilityChanges); + return; + } + mContent.completePendingPageChanges(); mContent.snapToPageImmediately(mContent.getDestinationPage()); diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java index 5e86bd6b8a..96a8da97f3 100644 --- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java +++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java @@ -33,6 +33,7 @@ import com.android.launcher3.model.BgDataModel.Callbacks; 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.ItemInfoWithIcon; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.WorkspaceItemFactory; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -102,6 +103,11 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask { Objects.requireNonNull(item.getIntent()))) { continue; } + + if (item instanceof ItemInfoWithIcon + && ((ItemInfoWithIcon) item).isArchived()) { + continue; + } } if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java index 9a3abd47fd..59f453a21c 100644 --- a/src/com/android/launcher3/model/ItemInstallQueue.java +++ b/src/com/android/launcher3/model/ItemInstallQueue.java @@ -18,10 +18,12 @@ package com.android.launcher3.model; import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID; +import static com.android.launcher3.Flags.enableSupportForArchiving; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; import static com.android.launcher3.model.data.AppInfo.makeLaunchIntent; +import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.appwidget.AppWidgetManager; @@ -276,6 +278,7 @@ public class ItemInstallQueue { return intent; } + @SuppressWarnings("NewApi") public Pair getItemInfo(Context context) { switch (itemType) { case ITEM_TYPE_APPLICATION: { @@ -297,6 +300,9 @@ public class ItemInstallQueue { } else { lai = laiList.get(0); si.intent = makeLaunchIntent(lai); + if (enableSupportForArchiving() && lai.getActivityInfo().isArchived) { + si.runtimeStatusFlags |= FLAG_ARCHIVED; + } } LauncherAppState.getInstance(context).getIconCache() .getTitleAndIcon(si, () -> lai, usePackageIcon, false); diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java index 315b5e3a93..5636405600 100644 --- a/src/com/android/launcher3/testing/TestInformationHandler.java +++ b/src/com/android/launcher3/testing/TestInformationHandler.java @@ -150,6 +150,12 @@ public class TestInformationHandler implements ResourceBasedOverride { }, this::getCurrentActivity); } + case TestProtocol.REQUEST_CELL_LAYOUT_BOARDER_HEIGHT: { + response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, + mDeviceProfile.cellLayoutBorderSpacePx.y); + return response; + } + case TestProtocol.REQUEST_SYSTEM_GESTURE_REGION: { return getUIProperty(Bundle::putParcelable, activity -> { WindowInsetsCompat insets = WindowInsetsCompat.toWindowInsetsCompat( diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java index 2b5aaf5520..50d8886980 100644 --- a/src/com/android/launcher3/util/PackageManagerHelper.java +++ b/src/com/android/launcher3/util/PackageManagerHelper.java @@ -16,6 +16,8 @@ package com.android.launcher3.util; +import static com.android.launcher3.Flags.enableSupportForArchiving; + import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; @@ -112,8 +114,7 @@ public class PackageManagerHelper { @NonNull final UserHandle user, final int flags) { try { ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user); - return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled - ? null : info; + return !isPackageInstalledOrArchived(info) || !info.enabled ? null : info; } catch (PackageManager.NameNotFoundException e) { return null; } @@ -253,4 +254,11 @@ public class PackageManagerHelper { } return 100; } + + /** Returns true in case app is installed on the device or in archived state. */ + @SuppressWarnings("NewApi") + private boolean isPackageInstalledOrArchived(ApplicationInfo info) { + return (info.flags & ApplicationInfo.FLAG_INSTALLED) != 0 || ( + enableSupportForArchiving() && info.isArchived); + } } diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java index c5317e3d5c..230a651698 100644 --- a/src/com/android/launcher3/views/ActivityContext.java +++ b/src/com/android/launcher3/views/ActivityContext.java @@ -154,6 +154,19 @@ public interface ActivityContext { return false; } + /** + * Handle user tapping on unsupported target when in split selection mode. + * See {@link #isSplitSelectionActive()} + * + * @return {@code true} if this method will handle the incorrect target selection, + * {@code false} if it could not be handled or if not possible to handle based on + * current split state + */ + default boolean handleIncorrectSplitTargetSelection() { + // Overridden + return false; + } + /** * The root view to support drag-and-drop and popup support. */ diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java index 76b6fdebad..c60e1a41cd 100644 --- a/src/com/android/launcher3/views/FloatingSurfaceView.java +++ b/src/com/android/launcher3/views/FloatingSurfaceView.java @@ -175,7 +175,6 @@ public class FloatingSurfaceView extends AbstractFloatingView implements if (!mTmpPosition.equals(mIconPosition)) { mIconPosition.set(mTmpPosition); - sendIconInfo(); LayoutParams lp = (LayoutParams) mSurfaceView.getLayoutParams(); lp.width = Math.round(mIconPosition.width()); @@ -184,6 +183,9 @@ public class FloatingSurfaceView extends AbstractFloatingView implements lp.topMargin = Math.round(mIconPosition.top); } } + + sendIconInfo(); + if (mIcon != null && iconChanged && !mIconBounds.isEmpty()) { // Record the icon display setCurrentIconVisible(true); @@ -197,7 +199,7 @@ public class FloatingSurfaceView extends AbstractFloatingView implements } private void sendIconInfo() { - if (mContract != null && !mIconPosition.isEmpty()) { + if (mContract != null) { mContract.sendEndPosition(mIconPosition, mLauncher, mSurfaceView.getSurfaceControl()); } } diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java index 9de7f62529..145ad8087e 100644 --- a/src/com/android/launcher3/widget/BaseWidgetSheet.java +++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java @@ -72,14 +72,21 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - mContentHorizontalMargin = getResources().getDimensionPixelSize( - R.dimen.widget_list_horizontal_margin); + mContentHorizontalMargin = getWidgetListHorizontalMargin(); mWidgetCellHorizontalPadding = getResources().getDimensionPixelSize( R.dimen.widget_cell_horizontal_padding); mNavBarScrimPaint = new Paint(); mNavBarScrimPaint.setColor(Themes.getNavBarScrimColor(mActivityContext)); } + /** + * Returns the margins to be applied to the left and right of the widget apps list. + */ + protected int getWidgetListHorizontalMargin() { + return getResources().getDimensionPixelSize( + R.dimen.widget_list_horizontal_margin); + } + protected int getScrimColor(Context context) { return context.getResources().getColor(R.color.widgets_picker_scrim); } @@ -142,8 +149,7 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView @Override public void setInsets(Rect insets) { mInsets.set(insets); - @Px int contentHorizontalMargin = getResources().getDimensionPixelSize( - R.dimen.widget_list_horizontal_margin); + @Px int contentHorizontalMargin = getWidgetListHorizontalMargin(); if (contentHorizontalMargin != mContentHorizontalMargin) { onContentHorizontalMarginChanged(contentHorizontalMargin); mContentHorizontalMargin = contentHorizontalMargin; diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java index 66562373d1..26c04f5116 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java @@ -302,6 +302,12 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { }; } + @Override + protected int getWidgetListHorizontalMargin() { + return getResources().getDimensionPixelSize( + R.dimen.widget_list_left_pane_horizontal_margin); + } + @Override protected boolean isTwoPane() { return true; diff --git a/tests/Android.bp b/tests/Android.bp index 310e418e48..ed8609e3d0 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -176,6 +176,7 @@ android_library { name: "launcher-testing-shared", srcs: [ "multivalentTests/shared/com/android/launcher3/testing/shared/**/*.java", + "multivalentTests/shared/com/android/launcher3/testing/shared/**/*.kt" ], resource_dirs: [], manifest: "multivalentTests/shared/AndroidManifest.xml", diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java index 3e188e6fb1..2f9945d17e 100644 --- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java +++ b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java @@ -121,6 +121,7 @@ public final class TestProtocol { public static final String REQUEST_IS_TABLET = "is-tablet"; public static final String REQUEST_NUM_ALL_APPS_COLUMNS = "num-all-apps-columns"; public static final String REQUEST_IS_TWO_PANELS = "is-two-panel"; + public static final String REQUEST_CELL_LAYOUT_BOARDER_HEIGHT = "cell-layout-boarder-height"; public static final String REQUEST_START_DRAG_THRESHOLD = "start-drag-threshold"; public static final String REQUEST_SHELL_DRAG_READY = "shell-drag-ready"; public static final String REQUEST_GET_ACTIVITIES_CREATED_COUNT = diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java index dbbdcf519a..62f2259ca3 100644 --- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java +++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java @@ -66,7 +66,7 @@ public class CellLayoutBoard implements Comparable { } public CellLayoutBoard(int width, int height) { - mWidget = new char[width + 1][height + 1]; + mWidget = new char[width][height]; this.mWidth = width; this.mHeight = height; for (int x = 0; x < mWidget.length; x++) { @@ -371,8 +371,8 @@ public class CellLayoutBoard implements Comparable { s.append("\n"); maxX = Math.min(maxX, mWidget.length); maxY = Math.min(maxY, mWidget[0].length); - for (int y = 0; y <= maxY; y++) { - for (int x = 0; x <= maxX; x++) { + for (int y = 0; y < maxY; y++) { + for (int x = 0; x < maxX; x++) { s.append(mWidget[x][y]); } s.append('\n'); diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt index 770024fb5d..fcfb3dbc76 100644 --- a/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt +++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt @@ -27,7 +27,7 @@ open class RandomBoardGenerator(generator: Random) : DeterministicRandomGenerato * usually less than 100. * @return a randomly generated board filled with icons and widgets. */ - open fun generateBoard(width: Int, height: Int, remainingEmptySpaces: Int): CellLayoutBoard? { + open fun generateBoard(width: Int, height: Int, remainingEmptySpaces: Int): CellLayoutBoard { val cellLayoutBoard = CellLayoutBoard(width, height) return fillBoard(cellLayoutBoard, Rect(0, 0, width, height), remainingEmptySpaces) } @@ -39,8 +39,8 @@ open class RandomBoardGenerator(generator: Random) : DeterministicRandomGenerato ): CellLayoutBoard { var remainingEmptySpaces = remainingEmptySpacesArg if (area.height() * area.width() <= 0) return board - val width = getRandom(1, area.width() - 1) - val height = getRandom(1, area.height() - 1) + val width = getRandom(1, area.width()) + val height = getRandom(1, area.height()) val x = area.left + getRandom(0, area.width() - width) val y = area.top + getRandom(0, area.height() - height) if (remainingEmptySpaces > 0) { diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index fef93b7626..6c9f5ed620 100644 --- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -405,6 +405,11 @@ public final class LauncherInstrumentation { .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD); } + int getCellLayoutBoarderHeight() { + return getTestInfo(TestProtocol.REQUEST_CELL_LAYOUT_BOARDER_HEIGHT) + .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); + } + int getFocusedTaskHeightForTablet() { return getTestInfo(TestProtocol.REQUEST_GET_FOCUSED_TASK_HEIGHT_FOR_TABLET).getInt( TestProtocol.TEST_INFO_RESPONSE_FIELD); diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java index d0573e077b..3895302794 100644 --- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java +++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/WidgetResizeFrame.java @@ -57,7 +57,8 @@ public class WidgetResizeFrame { Rect originalWidgetSize = widget.getVisibleBounds(); Point targetStart = bottomResizeHandle.getVisibleCenter(); Point targetDest = bottomResizeHandle.getVisibleCenter(); - targetDest.offset(0, originalWidgetSize.height()); + targetDest.offset(0, + originalWidgetSize.height() + mLauncher.getCellLayoutBoarderHeight()); final long downTime = SystemClock.uptimeMillis(); mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetStart, diff --git a/tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java b/tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java index 27a2c75795..ba74244bd0 100644 --- a/tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java +++ b/tests/src/com/android/launcher3/allapps/TaplAllAppsIconsWorkingTest.java @@ -62,5 +62,6 @@ public class TaplAllAppsIconsWorkingTest extends AbstractLauncherUiTest { } finally { allApps.unfreeze(); } + mLauncher.goHome(); } } diff --git a/tests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt b/tests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt new file mode 100644 index 0000000000..13dfd5eca8 --- /dev/null +++ b/tests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.celllayout + +import android.content.Context +import android.graphics.Point +import android.util.Log +import android.view.View +import androidx.core.view.get +import androidx.test.core.app.ApplicationProvider +import com.android.launcher3.CellLayout +import com.android.launcher3.celllayout.board.CellLayoutBoard +import com.android.launcher3.celllayout.board.IconPoint +import com.android.launcher3.celllayout.board.PermutedBoardComparator +import com.android.launcher3.celllayout.board.WidgetRect +import com.android.launcher3.celllayout.testgenerator.RandomBoardGenerator +import com.android.launcher3.util.ActivityContextWrapper +import com.android.launcher3.views.DoubleShadowBubbleTextView +import java.util.Random +import org.junit.Assert +import org.junit.Rule +import org.junit.Test + +private class HotseatReorderTestCase( + val startBoard: CellLayoutBoard, + val endBoard: CellLayoutBoard +) { + override fun toString(): String { + return "$startBoard#endBoard:\n$endBoard" + } +} + +class HotseatReorderUnitTest { + + private val applicationContext: Context = + ActivityContextWrapper(ApplicationProvider.getApplicationContext()) + + @JvmField @Rule var cellLayoutBuilder = UnitTestCellLayoutBuilderRule() + + /** + * This test generates random CellLayout configurations and then try to reorder it and makes + * sure the result is a valid board meaning it didn't remove any widget or icon. + */ + @Test + fun generateValidTests() { + val generator = Random(Companion.SEED.toLong()) + for (i in 0 until Companion.TOTAL_OF_CASES_GENERATED) { + // Using a new seed so that we can replicate the same test cases. + val seed = generator.nextInt() + Log.d(Companion.TAG, "Seed = $seed") + + val testCase: HotseatReorderTestCase = + generateRandomTestCase(RandomBoardGenerator(Random(seed.toLong()))) + Log.d(Companion.TAG, "testCase = $testCase") + + Assert.assertTrue( + "invalid case $i", + PermutedBoardComparator().compare(testCase.startBoard, testCase.endBoard) == 0 + ) + } + } + + private fun addViewInCellLayout( + cellLayout: CellLayout, + cellX: Int, + cellY: Int, + spanX: Int, + spanY: Int, + isWidget: Boolean + ) { + val cell = + if (isWidget) View(applicationContext) + else DoubleShadowBubbleTextView(applicationContext) + cell.layoutParams = CellLayoutLayoutParams(cellX, cellY, spanX, spanY) + cellLayout.addViewToCellLayout( + cell, + -1, + cell.id, + cell.layoutParams as CellLayoutLayoutParams, + true + ) + } + + private fun solve(board: CellLayoutBoard): CellLayout { + val cl = cellLayoutBuilder.createCellLayout(board.width, board.height, false) + // The views have to be sorted or the result can vary + board.icons + .map(IconPoint::getCoord) + .sortedWith( + Comparator.comparing { p: Any -> (p as Point).x } + .thenComparing { p: Any -> (p as Point).y } + ) + .forEach { p -> + addViewInCellLayout( + cellLayout = cl, + cellX = p.x, + cellY = p.y, + spanX = 1, + spanY = 1, + isWidget = false + ) + } + board.widgets + .sortedWith( + Comparator.comparing(WidgetRect::getCellX).thenComparing(WidgetRect::getCellY) + ) + .forEach { widget -> + addViewInCellLayout( + cl, + widget.cellX, + widget.cellY, + widget.spanX, + widget.spanY, + isWidget = true + ) + } + if (cl.makeSpaceForHotseatMigration(true)) { + commitTempPosition(cl) + } + return cl + } + + private fun commitTempPosition(cellLayout: CellLayout) { + val count = cellLayout.shortcutsAndWidgets.childCount + for (i in 0 until count) { + val params = cellLayout.shortcutsAndWidgets[i].layoutParams as CellLayoutLayoutParams + params.cellX = params.tmpCellX + params.cellY = params.tmpCellY + } + } + + private fun boardFromCellLayout(cellLayout: CellLayout): CellLayoutBoard { + val views = mutableListOf() + for (i in 0 until cellLayout.shortcutsAndWidgets.childCount) { + views.add(cellLayout.shortcutsAndWidgets.getChildAt(i)) + } + return CellLayoutTestUtils.viewsToBoard(views, cellLayout.countX, cellLayout.countY) + } + + private fun generateRandomTestCase( + boardGenerator: RandomBoardGenerator + ): HotseatReorderTestCase { + val width: Int = boardGenerator.getRandom(3, Companion.MAX_BOARD_SIZE) + val height: Int = boardGenerator.getRandom(3, Companion.MAX_BOARD_SIZE) + val targetWidth: Int = boardGenerator.getRandom(1, width - 2) + val targetHeight: Int = boardGenerator.getRandom(1, height - 2) + val board: CellLayoutBoard = + boardGenerator.generateBoard(width, height, targetWidth * targetHeight) + val finishBoard: CellLayoutBoard = boardFromCellLayout(solve(board)) + return HotseatReorderTestCase(board, finishBoard) + } + + companion object { + private const val MAX_BOARD_SIZE = 13 + + /** + * There is nothing special about this numbers, the random seed is just to be able to + * reproduce the test cases and the height and width is a random number similar to what + * users expect on their devices + */ + private const val SEED = -194162315 + private const val TOTAL_OF_CASES_GENERATED = 300 + private const val TAG = "HotseatReorderUnitTest" + } +} diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java index d96287f6f4..7aa26a1a50 100644 --- a/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java @@ -161,11 +161,12 @@ public class TaplAddConfigWidgetTest extends AbstractLauncherUiTest { public int getWidgetId() throws InterruptedException { Intent intent = blockingGetExtraIntent(); - assertNotNull(intent); - assertEquals(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE, intent.getAction()); + assertNotNull("Null EXTRA_INTENT", intent); + assertEquals("Intent action is not ACTION_APPWIDGET_CONFIGURE", + AppWidgetManager.ACTION_APPWIDGET_CONFIGURE, intent.getAction()); int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, LauncherAppWidgetInfo.NO_ID); - assertNotSame(widgetId, LauncherAppWidgetInfo.NO_ID); + assertNotSame("Widget id is NO_ID", widgetId, LauncherAppWidgetInfo.NO_ID); return widgetId; } } diff --git a/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java b/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java new file mode 100644 index 0000000000..d1da5f4f2d --- /dev/null +++ b/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.util; + +import static com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.LauncherApps; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; + +/** Unit tests for {@link PackageManagerHelper}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public final class PackageManagerHelperTest { + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + private static final String TEST_PACKAGE = "com.android.test.package"; + private static final int TEST_USER = 2; + + private Context mContext; + private LauncherApps mLauncherApps; + private PackageManagerHelper mPackageManagerHelper; + + @Before + public void setup() { + mContext = mock(Context.class); + mLauncherApps = mock(LauncherApps.class); + when(mContext.getSystemService(eq(LauncherApps.class))).thenReturn(mLauncherApps); + mPackageManagerHelper = new PackageManagerHelper(mContext); + } + + @Test + @RequiresFlagsEnabled(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING) + public void getApplicationInfo_archivedApp_appInfoIsNotNull() + throws PackageManager.NameNotFoundException { + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.isArchived = true; + when(mLauncherApps.getApplicationInfo(TEST_PACKAGE, 0 /* flags */, + UserHandle.of(TEST_USER))) + .thenReturn(applicationInfo); + + assertThat(mPackageManagerHelper.getApplicationInfo(TEST_PACKAGE, UserHandle.of(TEST_USER), + 0 /* flags */)) + .isNotNull(); + } +}