From 86a1b1abf5cb7f45be0dab9a8be386cbcf67c787 Mon Sep 17 00:00:00 2001 From: Rohit Goyal Date: Mon, 11 Mar 2024 12:27:19 +0000 Subject: [PATCH 1/9] Bugfixes: Handle session failure unarchival cases so that icon and title are accurate. * Remove old package icon entry from IconCache in case of a session failure. * Allow package entry to fallback to fallback state where missing icon & titles are filled using PackageManager. * Re-bind archived application in all apps if an unarchival fails. Test: verified bugfixes locally. Bug: 326494527 Bug: 328314141 Flag: ACONFIG com.android.launcher3.enable_support_for_archiving DEVELOPMENT Merged-In: Ib132bece397001e9a14933c2f6d733a04a089ef9 Change-Id: Ib132bece397001e9a14933c2f6d733a04a089ef9 --- src/com/android/launcher3/LauncherModel.java | 34 +++++++++++++------ .../launcher3/allapps/AppInfoComparator.java | 3 +- .../android/launcher3/icons/IconCache.java | 4 +-- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 99fca62ac1..be01d633bc 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -20,6 +20,7 @@ import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURC import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD; import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD; +import static com.android.launcher3.icons.cache.BaseIconCache.EMPTY_CLASS_NAME; import static com.android.launcher3.model.PackageUpdatedTask.OP_UPDATE; import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_AVAILABLE; import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_UNAVAILABLE; @@ -27,6 +28,7 @@ import static com.android.launcher3.testing.shared.TestProtocol.sDebugTracing; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInstaller; @@ -70,6 +72,7 @@ import com.android.launcher3.pm.UserCache; import com.android.launcher3.shortcuts.ShortcutRequest; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.ItemInfoMatcher; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; @@ -443,9 +446,18 @@ public class LauncherModel implements InstallSessionTracker.Callback { @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) { IconCache iconCache = app.getIconCache(); final IntSet removedIds = new IntSet(); - HashSet archivedItemsToCacheRefresh = new HashSet<>(); - HashSet archivedPackagesToCacheRefresh = new HashSet<>(); + HashSet archivedWorkspaceItemsToCacheRefresh = new HashSet<>(); + boolean isAppArchived = new PackageManagerHelper( + mApp.getContext()).isAppArchivedForUser(packageName, user); synchronized (dataModel) { + if (isAppArchived) { + // Remove package icon cache entry for archived app in case of a session + // failure. + mApp.getIconCache().remove( + new ComponentName(packageName, packageName + EMPTY_CLASS_NAME), + user); + } + for (ItemInfo info : dataModel.itemsIdMap) { if (info instanceof WorkspaceItemInfo && ((WorkspaceItemInfo) info).hasPromiseIconUi() @@ -456,19 +468,16 @@ public class LauncherModel implements InstallSessionTracker.Callback { } if (((WorkspaceItemInfo) info).isArchived()) { WorkspaceItemInfo workspaceItem = (WorkspaceItemInfo) info; - // Remove package cache icon for archived app in case of a session - // failure. - mApp.getIconCache().removeIconsForPkg(packageName, user); // Refresh icons on the workspace for archived apps. iconCache.getTitleAndIcon(workspaceItem, workspaceItem.usingLowResIcon()); - archivedPackagesToCacheRefresh.add(packageName); - archivedItemsToCacheRefresh.add(workspaceItem); + archivedWorkspaceItemsToCacheRefresh.add(workspaceItem); } } } - if (!archivedPackagesToCacheRefresh.isEmpty()) { - apps.updateIconsAndLabels(archivedPackagesToCacheRefresh, user); + + if (isAppArchived) { + apps.updateIconsAndLabels(new HashSet<>(List.of(packageName)), user); } } @@ -477,8 +486,11 @@ public class LauncherModel implements InstallSessionTracker.Callback { ItemInfoMatcher.ofItemIds(removedIds), "removed because install session failed"); } - if (!archivedItemsToCacheRefresh.isEmpty()) { - bindUpdatedWorkspaceItems(archivedItemsToCacheRefresh.stream().toList()); + if (!archivedWorkspaceItemsToCacheRefresh.isEmpty()) { + bindUpdatedWorkspaceItems( + archivedWorkspaceItemsToCacheRefresh.stream().toList()); + } + if (isAppArchived) { bindApplicationsIfNeeded(); } } diff --git a/src/com/android/launcher3/allapps/AppInfoComparator.java b/src/com/android/launcher3/allapps/AppInfoComparator.java index a0867dbaf1..bbf8e5a5fb 100644 --- a/src/com/android/launcher3/allapps/AppInfoComparator.java +++ b/src/com/android/launcher3/allapps/AppInfoComparator.java @@ -18,6 +18,7 @@ package com.android.launcher3.allapps; import android.content.Context; import android.os.Process; import android.os.UserHandle; +import android.text.TextUtils; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.pm.UserCache; @@ -64,7 +65,7 @@ public class AppInfoComparator implements Comparator { } private String getSortingTitle(AppInfo info) { - if (info.appTitle != null) { + if (!TextUtils.isEmpty(info.appTitle)) { return info.appTitle.toString(); } if (info.title != null) { diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index 41e3ef026b..1633eba5e1 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -222,6 +222,7 @@ public class IconCache extends BaseIconCache { * Updates {@param application} only if a valid entry is found. */ public synchronized void updateTitleAndIcon(AppInfo application) { + boolean preferPackageIcon = application.isArchived(); CacheEntry entry = cacheLocked(application.componentName, application.user, () -> null, mLauncherActivityInfoCachingLogic, false, application.usingLowResIcon()); @@ -229,13 +230,12 @@ public class IconCache extends BaseIconCache { return; } - boolean preferPackageIcon = application.isArchived(); if (preferPackageIcon) { String packageName = application.getTargetPackage(); CacheEntry packageEntry = cacheLocked(new ComponentName(packageName, packageName + EMPTY_CLASS_NAME), application.user, () -> null, mLauncherActivityInfoCachingLogic, - false, application.usingLowResIcon()); + true, application.usingLowResIcon()); applyPackageEntry(packageEntry, application, entry); } else { applyCacheEntry(entry, application); From c4ec531c9f3353276daa94308a78698e3609d97a Mon Sep 17 00:00:00 2001 From: Jordan Silva Date: Fri, 15 Mar 2024 16:00:24 +0000 Subject: [PATCH 2/9] Fix overview split task icon and app chips flipped and misaligned in fake orientation This CL fixes the app and icon chip misaligned and wrong positioned in fake landscape and seascape. The icon chip in seascape are still in RTL and it should be address in a different bug b/326377497. Fix: 320314835 Fix: 326383211 Fix: 326383010 Bug: 328028913 Flag: ACONFIG com.android.launcher3.enable_overview_icon_menu TEAMFOOD Flag: ACONFIG com.android.launcher3.enable_grid_only_overview TEAMFOOD Test: manual. instructions described in the bug report. Test: atest LandscapePagedViewHandlerTest Test: atest SeascapePagedViewHandlerTest Test: atest OverviewTaskSplitImageTest Change-Id: I9be07cc4f4cf5ee6e1da0d954ed7664d96bd330c --- .../orientation/LandscapePagedViewHandler.kt | 185 +++++++++------- .../orientation/PortraitPagedViewHandler.java | 1 + .../orientation/SeascapePagedViewHandler.kt | 177 +++++++--------- .../LandscapePagedViewHandlerTest.kt | 195 +++++++++++++++++ .../SeascapePagedViewHandlerTest.kt | 197 ++++++++++++++++++ 5 files changed, 577 insertions(+), 178 deletions(-) create mode 100644 quickstep/tests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt create mode 100644 quickstep/tests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt index 39fb1585d1..164010472a 100644 --- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt +++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt @@ -15,6 +15,7 @@ */ package com.android.quickstep.orientation +import android.annotation.SuppressLint import android.content.res.Resources import android.graphics.Point import android.graphics.PointF @@ -33,6 +34,7 @@ import android.view.ViewGroup import android.view.accessibility.AccessibilityEvent import android.widget.FrameLayout import android.widget.LinearLayout +import androidx.annotation.VisibleForTesting import androidx.core.util.component1 import androidx.core.util.component2 import com.android.launcher3.DeviceProfile @@ -44,7 +46,11 @@ import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds import com.android.launcher3.touch.PagedOrientationHandler.Float2DAction import com.android.launcher3.touch.PagedOrientationHandler.Int2DAction import com.android.launcher3.touch.SingleAxisSwipeDetector -import com.android.launcher3.util.SplitConfigurationOptions.* +import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT +import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT +import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED +import com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN +import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption import com.android.launcher3.util.SplitConfigurationOptions.StagePosition import com.android.launcher3.views.BaseDragLayer @@ -451,13 +457,8 @@ open class LandscapePagedViewHandler : RecentsPagedOrientationHandler { // (portrait bottom) and secondary is on the right (portrait top) val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx val totalThumbnailHeight = parentHeight - spaceAboveSnapshot - val dividerBar = - Math.round( - totalThumbnailHeight * - if (splitBoundsConfig.appsStackedVertically) - splitBoundsConfig.dividerHeightPercent - else splitBoundsConfig.dividerWidthPercent - ) + val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig) + val (taskViewFirst, taskViewSecond) = getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight) @@ -482,13 +483,8 @@ open class LandscapePagedViewHandler : RecentsPagedOrientationHandler { ): Pair { val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx val totalThumbnailHeight = parentHeight - spaceAboveSnapshot - val dividerBar = - Math.round( - totalThumbnailHeight * - if (splitBoundsConfig.appsStackedVertically) - splitBoundsConfig.dividerHeightPercent - else splitBoundsConfig.dividerWidthPercent - ) + val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig) + val taskPercent = if (splitBoundsConfig.appsStackedVertically) { splitBoundsConfig.topTaskPercent @@ -569,64 +565,22 @@ open class LandscapePagedViewHandler : RecentsPagedOrientationHandler { deviceProfile: DeviceProfile, splitConfig: SplitBounds ) { - val primaryIconParams = primaryIconView.layoutParams as FrameLayout.LayoutParams - val secondaryIconParams = - if (Flags.enableOverviewIconMenu()) - secondaryIconView.layoutParams as FrameLayout.LayoutParams - else FrameLayout.LayoutParams(primaryIconParams) + val spaceAboveSnapshot = deviceProfile.overviewTaskThumbnailTopMarginPx + val totalThumbnailHeight = groupedTaskViewHeight - spaceAboveSnapshot + val dividerBar: Int = getDividerBarSize(totalThumbnailHeight, splitConfig) - // We calculate the "midpoint" of the thumbnail area, and place the icons there. - // This is the place where the thumbnail area splits by default, in a near-50/50 split. - // It is usually not exactly 50/50, due to insets/screen cutouts. - val fullscreenInsetThickness = (deviceProfile.insets.top - deviceProfile.insets.bottom) - val fullscreenMidpointFromBottom = ((deviceProfile.heightPx - fullscreenInsetThickness) / 2) - val midpointFromBottomPct = fullscreenMidpointFromBottom.toFloat() / deviceProfile.heightPx - val insetPct = fullscreenInsetThickness.toFloat() / deviceProfile.heightPx - val spaceAboveSnapshots = deviceProfile.overviewTaskThumbnailTopMarginPx - val overviewThumbnailAreaThickness = groupedTaskViewHeight - spaceAboveSnapshots - val bottomToMidpointOffset = - (overviewThumbnailAreaThickness * midpointFromBottomPct).toInt() - val insetOffset = (overviewThumbnailAreaThickness * insetPct).toInt() - if (Flags.enableOverviewIconMenu()) { - val gravity = if (isRtl) Gravity.BOTTOM or Gravity.START else Gravity.TOP or Gravity.END - primaryIconParams.gravity = gravity - secondaryIconParams.gravity = gravity - } else { - primaryIconParams.gravity = Gravity.BOTTOM or if (isRtl) Gravity.START else Gravity.END - secondaryIconParams.gravity = - Gravity.BOTTOM or if (isRtl) Gravity.START else Gravity.END - } - primaryIconView.translationX = 0f - secondaryIconView.translationX = 0f - when { - Flags.enableOverviewIconMenu() -> { - val primaryAppChipView = primaryIconView as IconAppChipView - val secondaryAppChipView = secondaryIconView as IconAppChipView - if (primaryIconView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { - secondaryAppChipView.setSplitTranslationY(-primarySnapshotHeight.toFloat()) - primaryAppChipView.setSplitTranslationY(0f) - } else { - val secondarySnapshotHeight = groupedTaskViewHeight - primarySnapshotHeight - primaryAppChipView.setSplitTranslationY(secondarySnapshotHeight.toFloat()) - } - } - splitConfig.initiatedFromSeascape -> { - // if the split was initiated from seascape, - // the task on the right (secondary) is slightly larger - primaryIconView.translationY = (-bottomToMidpointOffset - insetOffset).toFloat() - secondaryIconView.translationY = - (-bottomToMidpointOffset - insetOffset + taskIconHeight).toFloat() - } - else -> { - // if not, - // the task on the left (primary) is slightly larger - primaryIconView.translationY = -bottomToMidpointOffset.toFloat() - secondaryIconView.translationY = - (-bottomToMidpointOffset + taskIconHeight).toFloat() - } - } - primaryIconView.layoutParams = primaryIconParams - secondaryIconView.layoutParams = secondaryIconParams + val (topLeftY, bottomRightY) = + getSplitIconsPosition( + taskIconHeight, + primarySnapshotHeight, + totalThumbnailHeight, + isRtl, + deviceProfile.overviewTaskMarginPx, + dividerBar + ) + + updateSplitIconsPosition(primaryIconView, topLeftY, isRtl) + updateSplitIconsPosition(secondaryIconView, bottomRightY, isRtl) } override fun getDefaultSplitPosition(deviceProfile: DeviceProfile): Int { @@ -656,4 +610,91 @@ open class LandscapePagedViewHandler : RecentsPagedOrientationHandler { override fun getFloatingTaskPrimaryTranslation(floatingTask: View, dp: DeviceProfile): Float = floatingTask.translationY + + /** + * Retrieves split icons position + * + * @param taskIconHeight The height of the task icon. + * @param primarySnapshotHeight The height for the primary snapshot (i.e., top-left snapshot). + * @param totalThumbnailHeight The total height for the group task view. + * @param isRtl Whether the layout direction is RTL (or false for LTR). + * @param overviewTaskMarginPx The space under the focused task icon provided by Device Profile. + * @param dividerSize The size of the divider for the group task view. + * @return The top-left and right-bottom positions for the icon views. + */ + @VisibleForTesting + open fun getSplitIconsPosition( + taskIconHeight: Int, + primarySnapshotHeight: Int, + totalThumbnailHeight: Int, + isRtl: Boolean, + overviewTaskMarginPx: Int, + dividerSize: Int, + ): SplitIconPositions { + return if (Flags.enableOverviewIconMenu()) { + if (isRtl) { + SplitIconPositions(0, -(totalThumbnailHeight - primarySnapshotHeight)) + } else { + SplitIconPositions(0, primarySnapshotHeight + dividerSize) + } + } else { + val topLeftY = primarySnapshotHeight + overviewTaskMarginPx + SplitIconPositions( + topLeftY = topLeftY, + bottomRightY = topLeftY + dividerSize + taskIconHeight + ) + } + } + + /** + * Updates icon view gravity and translation for split tasks + * + * @param iconView View to be updated + * @param translationY the translationY that should be applied + * @param isRtl Whether the layout direction is RTL (or false for LTR). + */ + @SuppressLint("RtlHardcoded") + @VisibleForTesting + open fun updateSplitIconsPosition(iconView: View, translationY: Int, isRtl: Boolean) { + val layoutParams = iconView.layoutParams as FrameLayout.LayoutParams + + if (Flags.enableOverviewIconMenu()) { + val appChipView = iconView as IconAppChipView + layoutParams.gravity = + if (isRtl) Gravity.BOTTOM or Gravity.START else Gravity.TOP or Gravity.END + appChipView.layoutParams = layoutParams + appChipView.setSplitTranslationX(0f) + appChipView.setSplitTranslationY(translationY.toFloat()) + } else { + layoutParams.gravity = Gravity.TOP or Gravity.RIGHT + layoutParams.topMargin = translationY + iconView.translationX = 0f + iconView.translationY = 0f + iconView.layoutParams = layoutParams + } + } + + /** + * It calculates the divider's size in the group task view. + * + * @param totalThumbnailHeight The total height for the group task view + * @param splitConfig Contains information about sizes and proportions for split task. + * @return The divider size for the group task view. + */ + protected fun getDividerBarSize(totalThumbnailHeight: Int, splitConfig: SplitBounds): Int { + return Math.round( + totalThumbnailHeight * + if (splitConfig.appsStackedVertically) splitConfig.dividerHeightPercent + else splitConfig.dividerWidthPercent + ) + } + + /** + * Data structure to keep the y position to be used for the split task icon views translation. + * + * @param topLeftY The y-axis position for the task view position on the Top or Left side. + * @param bottomRightY The y-axis position for the task view position on the Bottom or Right + * side. + */ + data class SplitIconPositions(val topLeftY: Int, val bottomRightY: Int) } diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java index 62dfd82c9f..0476fe80d6 100644 --- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java +++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java @@ -75,6 +75,7 @@ public class PortraitPagedViewHandler extends DefaultPagedViewHandler implements public T getSecondaryValue(T x, T y) { return y; } + @Override public boolean isLayoutNaturalToLauncher() { return true; diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt index 0c78b8f9cf..5bebf8c8be 100644 --- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt +++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt @@ -15,6 +15,7 @@ */ package com.android.quickstep.orientation +import android.annotation.SuppressLint import android.content.res.Resources import android.graphics.Point import android.graphics.PointF @@ -32,7 +33,12 @@ import com.android.launcher3.Flags import com.android.launcher3.R import com.android.launcher3.Utilities import com.android.launcher3.touch.SingleAxisSwipeDetector -import com.android.launcher3.util.SplitConfigurationOptions.* +import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT +import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT +import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED +import com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN +import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds +import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption import com.android.launcher3.views.BaseDragLayer import com.android.quickstep.views.IconAppChipView @@ -255,97 +261,6 @@ class SeascapePagedViewHandler : LandscapePagedViewHandler() { iconAppChipView.setRotation(degreesRotated) } - override fun setSplitIconParams( - primaryIconView: View, - secondaryIconView: View, - taskIconHeight: Int, - primarySnapshotWidth: Int, - primarySnapshotHeight: Int, - groupedTaskViewHeight: Int, - groupedTaskViewWidth: Int, - isRtl: Boolean, - deviceProfile: DeviceProfile, - splitConfig: SplitBounds - ) { - super.setSplitIconParams( - primaryIconView, - secondaryIconView, - taskIconHeight, - primarySnapshotWidth, - primarySnapshotHeight, - groupedTaskViewHeight, - groupedTaskViewWidth, - isRtl, - deviceProfile, - splitConfig - ) - val primaryIconParams = primaryIconView.layoutParams as FrameLayout.LayoutParams - val secondaryIconParams = secondaryIconView.layoutParams as FrameLayout.LayoutParams - - // We calculate the "midpoint" of the thumbnail area, and place the icons there. - // This is the place where the thumbnail area splits by default, in a near-50/50 split. - // It is usually not exactly 50/50, due to insets/screen cutouts. - val fullscreenInsetThickness = (deviceProfile.insets.top - deviceProfile.insets.bottom) - val fullscreenMidpointFromBottom = (deviceProfile.heightPx - fullscreenInsetThickness) / 2 - val midpointFromBottomPct = fullscreenMidpointFromBottom.toFloat() / deviceProfile.heightPx - val insetPct = fullscreenInsetThickness.toFloat() / deviceProfile.heightPx - val spaceAboveSnapshots = deviceProfile.overviewTaskThumbnailTopMarginPx - val overviewThumbnailAreaThickness = groupedTaskViewHeight - spaceAboveSnapshots - val bottomToMidpointOffset = - (overviewThumbnailAreaThickness * midpointFromBottomPct).toInt() - val insetOffset = (overviewThumbnailAreaThickness * insetPct).toInt() - val gravity = if (isRtl) Gravity.TOP or Gravity.END else Gravity.BOTTOM or Gravity.START - primaryIconParams.gravity = gravity - secondaryIconParams.gravity = gravity - primaryIconView.translationX = 0f - secondaryIconView.translationX = 0f - when { - Flags.enableOverviewIconMenu() -> { - val primaryAppChipView = primaryIconView as IconAppChipView - val secondaryAppChipView = secondaryIconView as IconAppChipView - if (isRtl) { - primaryAppChipView.setSplitTranslationY( - (groupedTaskViewHeight - primarySnapshotHeight).toFloat() - ) - secondaryAppChipView.setSplitTranslationY(0f) - } else { - secondaryAppChipView.setSplitTranslationY(-primarySnapshotHeight.toFloat()) - primaryAppChipView.setSplitTranslationY(0f) - } - } - splitConfig.initiatedFromSeascape -> { - // if the split was initiated from seascape, - // the task on the right (secondary) is slightly larger - if (isRtl) { - primaryIconView.translationY = - (bottomToMidpointOffset - insetOffset + taskIconHeight).toFloat() - secondaryIconView.translationY = - (bottomToMidpointOffset - insetOffset).toFloat() - } else { - primaryIconView.translationY = - (-bottomToMidpointOffset - insetOffset + taskIconHeight).toFloat() - secondaryIconView.translationY = - (-bottomToMidpointOffset - insetOffset).toFloat() - } - } - else -> { - // if not, - // the task on the left (primary) is slightly larger - if (isRtl) { - primaryIconView.translationY = - (bottomToMidpointOffset + taskIconHeight).toFloat() - secondaryIconView.translationY = bottomToMidpointOffset.toFloat() - } else { - primaryIconView.translationY = - (-bottomToMidpointOffset + taskIconHeight).toFloat() - secondaryIconView.translationY = -bottomToMidpointOffset.toFloat() - } - } - } - primaryIconView.layoutParams = primaryIconParams - secondaryIconView.layoutParams = secondaryIconParams - } - override fun measureGroupedTaskViewThumbnailBounds( primarySnapshot: View, secondarySnapshot: View, @@ -366,13 +281,8 @@ class SeascapePagedViewHandler : LandscapePagedViewHandler() { // (portrait bottom) and secondary is on the right (portrait top) val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx val totalThumbnailHeight = parentHeight - spaceAboveSnapshot - val dividerBar = - Math.round( - totalThumbnailHeight * - if (splitBoundsConfig.appsStackedVertically) - splitBoundsConfig.dividerHeightPercent - else splitBoundsConfig.dividerWidthPercent - ) + val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig) + val (taskViewFirst, taskViewSecond) = getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight) secondarySnapshot.translationY = 0f @@ -398,13 +308,8 @@ class SeascapePagedViewHandler : LandscapePagedViewHandler() { // (portrait bottom) and secondary is on the right (portrait top) val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx val totalThumbnailHeight = parentHeight - spaceAboveSnapshot - val dividerBar = - Math.round( - totalThumbnailHeight * - if (splitBoundsConfig.appsStackedVertically) - splitBoundsConfig.dividerHeightPercent - else splitBoundsConfig.dividerWidthPercent - ) + val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig) + val taskPercent = if (splitBoundsConfig.appsStackedVertically) { splitBoundsConfig.topTaskPercent @@ -430,4 +335,64 @@ class SeascapePagedViewHandler : LandscapePagedViewHandler() { override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = if (isRtl) -1 else 1 /* -------------------- */ + + override fun getSplitIconsPosition( + taskIconHeight: Int, + primarySnapshotHeight: Int, + totalThumbnailHeight: Int, + isRtl: Boolean, + overviewTaskMarginPx: Int, + dividerSize: Int, + ): SplitIconPositions { + return if (Flags.enableOverviewIconMenu()) { + if (isRtl) { + SplitIconPositions( + topLeftY = totalThumbnailHeight - primarySnapshotHeight, + bottomRightY = 0 + ) + } else { + SplitIconPositions( + topLeftY = 0, + bottomRightY = -(primarySnapshotHeight + dividerSize) + ) + } + } else { + // In seascape, the icons are initially placed at the bottom start of the + // display (portrait locked). The values defined here are used to translate the icons + // from the bottom to the almost-center of the screen using the bottom margin. + // The primary snapshot is placed at the bottom, thus we translate the icons using + // the size of the primary snapshot minus the icon size for the top-left icon. + SplitIconPositions( + topLeftY = primarySnapshotHeight - taskIconHeight, + bottomRightY = primarySnapshotHeight + dividerSize + ) + } + } + + /** + * Updates icon view gravity and translation for split tasks + * + * @param iconView View to be updated + * @param translationY the translationY that should be applied + * @param isRtl Whether the layout direction is RTL (or false for LTR). + */ + @SuppressLint("RtlHardcoded") + override fun updateSplitIconsPosition(iconView: View, translationY: Int, isRtl: Boolean) { + val layoutParams = iconView.layoutParams as FrameLayout.LayoutParams + + if (Flags.enableOverviewIconMenu()) { + val appChipView = iconView as IconAppChipView + layoutParams.gravity = + if (isRtl) Gravity.TOP or Gravity.END else Gravity.BOTTOM or Gravity.START + appChipView.layoutParams = layoutParams + appChipView.setSplitTranslationX(0f) + appChipView.setSplitTranslationY(translationY.toFloat()) + } else { + layoutParams.gravity = Gravity.BOTTOM or Gravity.LEFT + iconView.translationX = 0f + iconView.translationY = 0f + layoutParams.bottomMargin = translationY + iconView.layoutParams = layoutParams + } + } } diff --git a/quickstep/tests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt b/quickstep/tests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt new file mode 100644 index 0000000000..ea52842e22 --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt @@ -0,0 +1,195 @@ +/* + * 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.quickstep.orientation + +import android.platform.test.flag.junit.SetFlagsRule +import android.view.Gravity +import android.view.View +import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.launcher3.Flags +import com.android.quickstep.orientation.LandscapePagedViewHandler.SplitIconPositions +import com.android.quickstep.views.IconAppChipView +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.`when` +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +@RunWith(AndroidJUnit4::class) +class LandscapePagedViewHandlerTest { + + @get:Rule val setFlagsRule = SetFlagsRule() + + private val sut = LandscapePagedViewHandler() + + private fun enableGridOnlyOverview(isEnabled: Boolean) { + if (isEnabled) { + setFlagsRule.enableFlags( + Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, + Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU + ) + } else { + setFlagsRule.disableFlags( + Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, + Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU + ) + } + } + + /** [ Test getSplitIconsPosition ] */ + private fun getSplitIconsPosition(isRTL: Boolean): SplitIconPositions { + return sut.getSplitIconsPosition( + TASK_ICON_HEIGHT_PX, + PRIMARY_SNAPSHOT, + TOTAL_THUMBNAIL_HEIGHT, + isRTL, + OVERVIEW_TASK_MARGIN_PX, + DIVIDER_SIZE_PX, + ) + } + + @Test + fun testIcon_getSplitIconsPositions() { + enableGridOnlyOverview(false) + + val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = false) + + // Top-Left icon should be at the end of the primary snapshot height + assertThat(topLeftY).isEqualTo(250) + // Bottom-Right icon should be at the end of the primary height + divider + icon size + assertThat(bottomRightY).isEqualTo(374) + } + + @Test + fun testIcon_getSplitIconsPositions_isRTL() { + enableGridOnlyOverview(false) + + val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = true) + + // Top-Left icon should be at the end of the primary snapshot height + assertThat(topLeftY).isEqualTo(250) + // Bottom-Right icon should be at the end of the primary height + divider + icon size + assertThat(bottomRightY).isEqualTo(374) + } + + @Test + fun testChip_getSplitIconsPositions() { + enableGridOnlyOverview(true) + + val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = false) + + // Top-Left app chip should always be at the initial position of the first snapshot + assertThat(topLeftY).isEqualTo(0) + // Bottom-Right app chip should be at the end of the primary height + divider + assertThat(bottomRightY).isEqualTo(266) + } + + @Test + fun testChip_getSplitIconsPositions_isRTL() { + enableGridOnlyOverview(true) + + val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = true) + + // TODO(b/326377497): When started in fake seascape and rotated to landscape, + // the icon chips are in RTL and wrongly positioned at the right side of the snapshot. + // Top-Left app chip should be placed at the top left of the first snapshot, but because + // this issue, it's displayed at the top-right of the second snapshot. + // The Bottom-Right app chip is displayed at the top-right of the first snapshot because + // of this issue. + assertThat(topLeftY).isEqualTo(0) + assertThat(bottomRightY).isEqualTo(-316) + } + + /** Test updateSplitIconsPosition */ + @Test + fun testIcon_updateSplitIconsPosition() { + enableGridOnlyOverview(false) + + val expectedTranslationY = 250 + val expectedGravity = Gravity.TOP or Gravity.RIGHT + + val iconView = mock() + val frameLayout = FrameLayout.LayoutParams(100, 100) + `when`(iconView.layoutParams).thenReturn(frameLayout) + + sut.updateSplitIconsPosition(iconView, expectedTranslationY, false) + assertThat(frameLayout.gravity).isEqualTo(expectedGravity) + assertThat(frameLayout.topMargin).isEqualTo(expectedTranslationY) + verify(iconView).translationX = 0f + verify(iconView).translationY = 0f + } + + @Test + fun testIcon_updateSplitIconsPosition_isRTL() { + enableGridOnlyOverview(false) + + val expectedTranslationY = 250 + val expectedGravity = Gravity.TOP or Gravity.RIGHT + + val iconView = mock() + val frameLayout = FrameLayout.LayoutParams(100, 100) + `when`(iconView.layoutParams).thenReturn(frameLayout) + + sut.updateSplitIconsPosition(iconView, expectedTranslationY, true) + assertThat(frameLayout.gravity).isEqualTo(expectedGravity) + assertThat(frameLayout.topMargin).isEqualTo(expectedTranslationY) + verify(iconView).translationX = 0f + verify(iconView).translationY = 0f + } + + @Test + fun testChip_updateSplitIconsPosition() { + enableGridOnlyOverview(true) + + val expectedTranslationY = 250 + val frameLayout = FrameLayout.LayoutParams(100, 100) + val iconView = mock() + `when`(iconView.layoutParams).thenReturn(frameLayout) + + sut.updateSplitIconsPosition(iconView, expectedTranslationY, false) + assertThat(frameLayout.gravity).isEqualTo(Gravity.TOP or Gravity.END) + verify(iconView).setSplitTranslationX(0f) + verify(iconView).setSplitTranslationY(expectedTranslationY.toFloat()) + } + + @Test + fun testChip_updateSplitIconsPosition_isRTL() { + enableGridOnlyOverview(true) + + val expectedTranslationY = 250 + val frameLayout = FrameLayout.LayoutParams(100, 100) + val iconView = mock() + `when`(iconView.layoutParams).thenReturn(frameLayout) + + sut.updateSplitIconsPosition(iconView, expectedTranslationY, true) + assertThat(frameLayout.gravity).isEqualTo(Gravity.BOTTOM or Gravity.START) + verify(iconView).setSplitTranslationX(0f) + verify(iconView).setSplitTranslationY(expectedTranslationY.toFloat()) + } + + private companion object { + const val TASK_ICON_HEIGHT_PX = 108 + const val OVERVIEW_TASK_MARGIN_PX = 0 + const val DIVIDER_SIZE_PX = 16 + const val PRIMARY_SNAPSHOT = 250 + const val SECONDARY_SNAPSHOT = 300 + const val TOTAL_THUMBNAIL_HEIGHT = PRIMARY_SNAPSHOT + SECONDARY_SNAPSHOT + DIVIDER_SIZE_PX + } +} diff --git a/quickstep/tests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt b/quickstep/tests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt new file mode 100644 index 0000000000..2bc182c02d --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt @@ -0,0 +1,197 @@ +/* + * 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.quickstep.orientation + +import android.platform.test.flag.junit.SetFlagsRule +import android.view.Gravity +import android.view.View +import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.launcher3.Flags +import com.android.quickstep.orientation.LandscapePagedViewHandler.SplitIconPositions +import com.android.quickstep.views.IconAppChipView +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.`when` +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +@RunWith(AndroidJUnit4::class) +class SeascapePagedViewHandlerTest { + + @get:Rule val setFlagsRule = SetFlagsRule() + + private val sut = SeascapePagedViewHandler() + + private fun enableGridOnlyOverview(isEnabled: Boolean) { + if (isEnabled) { + setFlagsRule.enableFlags( + Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, + Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU + ) + } else { + setFlagsRule.disableFlags( + Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW, + Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU + ) + } + } + + /** [ Test getSplitIconsPosition ] */ + private fun getSplitIconsPosition(isRTL: Boolean): SplitIconPositions { + return sut.getSplitIconsPosition( + TASK_ICON_HEIGHT_PX, + PRIMARY_SNAPSHOT, + TOTAL_THUMBNAIL_HEIGHT, + isRTL, + OVERVIEW_TASK_MARGIN_PX, + DIVIDER_SIZE_PX, + ) + } + + @Test + fun testIcon_getSplitIconsPositions() { + enableGridOnlyOverview(false) + + val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = false) + + // The top-left icon is translated from the bottom of the screen to the end of + // the primary snapshot minus the icon size. + assertThat(topLeftY).isEqualTo(142) + // The bottom-right icon is placed at the end of the primary snapshot plus the divider. + assertThat(bottomRightY).isEqualTo(266) + } + + @Test + fun testIcon_getSplitIconsPositions_isRTL() { + enableGridOnlyOverview(false) + + val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = true) + + // The top-left icon is translated from the bottom of the screen to the end of + // the primary snapshot minus the icon size. + assertThat(topLeftY).isEqualTo(142) + // The bottom-right icon is placed at the end of the primary snapshot plus the divider. + assertThat(bottomRightY).isEqualTo(266) + } + + @Test + fun testChip_getSplitIconsPositions() { + enableGridOnlyOverview(true) + + val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = false) + + // Top-Left app chip should always be at the initial position of the first snapshot + assertThat(topLeftY).isEqualTo(0) + // Bottom-Right app chip should be at the end of the primary height + divider + assertThat(bottomRightY).isEqualTo(-266) + } + + @Test + fun testChip_getSplitIconsPositions_isRTL() { + enableGridOnlyOverview(true) + + val (topLeftY, bottomRightY) = getSplitIconsPosition(isRTL = true) + + // TODO(b/326377497): When started in fake seascape and rotated to landscape, + // the icon chips are in RTL and wrongly positioned at the right side of the snapshot. + // Top-Left app chip should be placed at the top left of the first snapshot, but because + // this issue, it's displayed at the top-right of the second snapshot. + // The Bottom-Right app chip is displayed at the top-right of the first snapshot because + // of this issue. + assertThat(topLeftY).isEqualTo(316) + assertThat(bottomRightY).isEqualTo(0) + } + + /** Test updateSplitIconsPosition */ + @Test + fun testIcon_updateSplitIconsPosition() { + enableGridOnlyOverview(false) + + val expectedTranslationY = 250 + val expectedGravity = Gravity.BOTTOM or Gravity.LEFT + + val iconView = mock() + val frameLayout = FrameLayout.LayoutParams(100, 100) + `when`(iconView.layoutParams).thenReturn(frameLayout) + + sut.updateSplitIconsPosition(iconView, expectedTranslationY, false) + assertThat(frameLayout.gravity).isEqualTo(expectedGravity) + assertThat(frameLayout.bottomMargin).isEqualTo(expectedTranslationY) + verify(iconView).translationX = 0f + verify(iconView).translationY = 0f + } + + @Test + fun testIcon_updateSplitIconsPosition_isRTL() { + enableGridOnlyOverview(false) + + val expectedTranslationY = 250 + val expectedGravity = Gravity.BOTTOM or Gravity.LEFT + + val iconView = mock() + val frameLayout = FrameLayout.LayoutParams(100, 100) + `when`(iconView.layoutParams).thenReturn(frameLayout) + + sut.updateSplitIconsPosition(iconView, expectedTranslationY, true) + assertThat(frameLayout.gravity).isEqualTo(expectedGravity) + assertThat(frameLayout.bottomMargin).isEqualTo(expectedTranslationY) + verify(iconView).translationX = 0f + verify(iconView).translationY = 0f + } + + @Test + fun testChip_updateSplitIconsPosition() { + enableGridOnlyOverview(true) + + val expectedTranslationY = 250 + val frameLayout = FrameLayout.LayoutParams(100, 100) + val iconView = mock() + `when`(iconView.layoutParams).thenReturn(frameLayout) + + sut.updateSplitIconsPosition(iconView, expectedTranslationY, false) + assertThat(frameLayout.gravity).isEqualTo(Gravity.BOTTOM or Gravity.START) + verify(iconView).setSplitTranslationX(0f) + verify(iconView).setSplitTranslationY(expectedTranslationY.toFloat()) + } + + @Test + fun testChip_updateSplitIconsPosition_isRTL() { + enableGridOnlyOverview(true) + + val expectedTranslationY = 250 + val frameLayout = FrameLayout.LayoutParams(100, 100) + val iconView = mock() + `when`(iconView.layoutParams).thenReturn(frameLayout) + + sut.updateSplitIconsPosition(iconView, expectedTranslationY, true) + assertThat(frameLayout.gravity).isEqualTo(Gravity.TOP or Gravity.END) + verify(iconView).setSplitTranslationX(0f) + verify(iconView).setSplitTranslationY(expectedTranslationY.toFloat()) + } + + private companion object { + const val TASK_ICON_HEIGHT_PX = 108 + const val OVERVIEW_TASK_MARGIN_PX = 0 + const val DIVIDER_SIZE_PX = 16 + const val PRIMARY_SNAPSHOT = 250 + const val SECONDARY_SNAPSHOT = 300 + const val TOTAL_THUMBNAIL_HEIGHT = PRIMARY_SNAPSHOT + SECONDARY_SNAPSHOT + DIVIDER_SIZE_PX + } +} From 7ce42078da03d8d924e34233fefd7c058960641b Mon Sep 17 00:00:00 2001 From: Vinit Nayak Date: Wed, 13 Mar 2024 18:18:56 -0700 Subject: [PATCH 3/9] Add CUJ Jank interactions for App Pair saving and launching * Added finishCallback consumer to LauncherAccessibilityDelegate to inform when adding an item to workspace was completed. * The logic seemed to be dependent on the parameter "focusForAccessibility", but all callers of that are currently passing in true Bug: 328646540 Test: https://paste.googleplex.com/6232597136408576 Newly added CUJs showing up when playing w/ device Change-Id: I5dce4b7e83fb17cc3a70565bfd5ce100a4cf72bb --- .../taskbar/TaskbarActivityContext.java | 2 +- .../uioverrides/QuickstepLauncher.java | 4 +- .../quickstep/util/AppPairsController.java | 26 ++++++++++-- .../util/SplitSelectStateController.java | 16 ++++++++ .../quickstep/util/AppPairsControllerTest.kt | 20 +++++----- .../LauncherAccessibilityDelegate.java | 40 +++++++++++++------ 6 files changed, 80 insertions(+), 28 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 27a895c3d4..a980af5ac1 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -1249,7 +1249,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext { if (findExactPairMatch) { // We did not find the app pair we were looking for, so launch one. recents.getSplitSelectController().getAppPairsController().launchAppPair( - (AppPairIcon) launchingIconView); + (AppPairIcon) launchingIconView, -1 /*cuj*/); } else { startItemInfoActivity(itemInfos.get(0)); } diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 2c45129b8b..ea0724662c 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -21,6 +21,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEAS import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED; import static com.android.app.animation.Interpolators.EMPHASIZED; +import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE; import static com.android.launcher3.Flags.enablePredictiveBackGesture; import static com.android.launcher3.Flags.enableUnfoldStateAnimation; import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO; @@ -1346,7 +1347,8 @@ public class QuickstepLauncher extends Launcher { * Launches two apps as an app pair. */ public void launchAppPair(AppPairIcon appPairIcon) { - mSplitSelectStateController.getAppPairsController().launchAppPair(appPairIcon); + mSplitSelectStateController.getAppPairsController().launchAppPair(appPairIcon, + CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE); } public boolean canStartHomeSafely() { diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java index 757f1f809b..ecb6118e46 100644 --- a/quickstep/src/com/android/quickstep/util/AppPairsController.java +++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java @@ -19,6 +19,7 @@ package com.android.quickstep.util; import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR; 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; @@ -40,6 +41,7 @@ import android.util.Pair; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.android.internal.jank.Cuj; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; @@ -62,6 +64,7 @@ import com.android.quickstep.TopTaskTracker; import com.android.quickstep.views.GroupedTaskView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.system.InteractionJankMonitorWrapper; import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition; import java.util.Arrays; @@ -112,6 +115,7 @@ public class AppPairsController { * well on trampoline apps). */ public void saveAppPair(GroupedTaskView gtv) { + InteractionJankMonitorWrapper.begin(gtv, Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR); TaskView.TaskIdAttributeContainer[] attributes = gtv.getTaskIdAttributeContainers(); WorkspaceItemInfo recentsInfo1 = attributes[0].getItemInfo(); WorkspaceItemInfo recentsInfo2 = attributes[1].getItemInfo(); @@ -168,7 +172,13 @@ public class AppPairsController { LauncherAccessibilityDelegate delegate = Launcher.getLauncher(mContext).getAccessibilityDelegate(); if (delegate != null) { - delegate.addToWorkspace(newAppPair, true); + delegate.addToWorkspace(newAppPair, true, (success) -> { + if (success) { + InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR); + } else { + InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR); + } + }); mStatsLogManager.logger().withItemInfo(newAppPair) .log(StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_SAVE); } @@ -179,12 +189,18 @@ public class AppPairsController { /** * Launches an app pair by searching the RecentsModel for running instances of each app, and * staging either those running instances or launching the apps as new Intents. + * + * @param cuj Should be an integer from {@link Cuj} or -1 if no CUJ needs to be logged for jank + * monitoring */ - public void launchAppPair(AppPairIcon appPairIcon) { + public void launchAppPair(AppPairIcon appPairIcon, int cuj) { WorkspaceItemInfo app1 = appPairIcon.getInfo().contents.get(0); WorkspaceItemInfo app2 = appPairIcon.getInfo().contents.get(1); ComponentKey app1Key = new ComponentKey(app1.getTargetComponent(), app1.user); ComponentKey app2Key = new ComponentKey(app2.getTargetComponent(), app2.user); + mSplitSelectStateController.setLaunchingCuj(cuj); + InteractionJankMonitorWrapper.begin(appPairIcon, cuj); + mSplitSelectStateController.findLastActiveTasksAndRunCallback( Arrays.asList(app1Key, app2Key), false /* findExactPairMatch */, @@ -343,7 +359,8 @@ public class AppPairsController { && !lastActiveTasksOfAppPair.contains(runningTaskId2)) { // Neither A nor B are on screen, so just launch a new app pair // normally. - launchAppPair(launchingIconView); + launchAppPair(launchingIconView, + CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR); } else { // Exactly one app (A or B) is on-screen, so we have to launch the other // on the appropriate side. @@ -388,7 +405,8 @@ public class AppPairsController { if (!task1IsOnScreen && !task2IsOnScreen) { // Neither App A nor App B are on-screen, launch the app pair normally. - launchAppPair(launchingIconView); + launchAppPair(launchingIconView, + CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR); } else { // Either A or B is on-screen, so launch the other on the appropriate // side. diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index 8e2520e2c4..44da8b13cc 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -104,6 +104,7 @@ import com.android.quickstep.views.SplitInstructionsView; import com.android.systemui.animation.RemoteAnimationRunnerCompat; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.InteractionJankMonitorWrapper; import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition; import com.android.wm.shell.splitscreen.ISplitSelectListener; @@ -151,6 +152,12 @@ public class SplitSelectStateController { /** True when the first selected split app is being launched in fullscreen. */ private boolean mLaunchingFirstAppFullscreen; + /** + * Should be a constant from {@link com.android.internal.jank.Cuj} or -1, does not need to be + * set for all launches. + */ + private int mLaunchCuj = -1; + private FloatingTaskView mFirstFloatingTaskView; private SplitInstructionsView mSplitInstructionsView; @@ -707,6 +714,10 @@ public class SplitSelectStateController { return mSplitAnimationController; } + public void setLaunchingCuj(int launchCuj) { + mLaunchCuj = launchCuj; + } + /** * Requires Shell Transitions */ @@ -850,6 +861,11 @@ public class SplitSelectStateController { mSplitInstructionsView = null; mLaunchingFirstAppFullscreen = false; + if (mLaunchCuj != -1) { + InteractionJankMonitorWrapper.end(mLaunchCuj); + } + mLaunchCuj = -1; + if (mSessionInstanceIds != null) { mStatsLogManager.logger() .withInstanceId(mSessionInstanceIds.second) diff --git a/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt index 510faf6132..adaf7ff8c3 100644 --- a/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt +++ b/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt @@ -105,7 +105,7 @@ class AppPairsControllerTest { whenever(mockTopTaskTracker.getCachedTopTask(any())).thenReturn(mockCachedTaskInfo) whenever(mockTask1.getKey()).thenReturn(mockTaskKey1) whenever(mockTask2.getKey()).thenReturn(mockTaskKey2) - doNothing().whenever(spyAppPairsController).launchAppPair(any()) + doNothing().whenever(spyAppPairsController).launchAppPair(any(), any()) doNothing() .whenever(spyAppPairsController) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) @@ -210,7 +210,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchAppPair and launchToSide were never called - verify(spyAppPairsController, never()).launchAppPair(any()) + verify(spyAppPairsController, never()).launchAppPair(any(), any()) verify(spyAppPairsController, never()) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) } @@ -234,7 +234,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchToSide was called with the correct arguments - verify(spyAppPairsController, never()).launchAppPair(any()) + verify(spyAppPairsController, never()).launchAppPair(any(), any()) verify(spyAppPairsController, times(1)) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_BOTTOM_OR_RIGHT)) } @@ -258,7 +258,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchToSide was called with the correct arguments - verify(spyAppPairsController, never()).launchAppPair(any()) + verify(spyAppPairsController, never()).launchAppPair(any(), any()) verify(spyAppPairsController, times(1)) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT)) } @@ -282,7 +282,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchToSide was called with the correct arguments - verify(spyAppPairsController, never()).launchAppPair(any()) + verify(spyAppPairsController, never()).launchAppPair(any(), any()) verify(spyAppPairsController, times(1)) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_BOTTOM_OR_RIGHT)) } @@ -306,7 +306,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchToSide was called with the correct arguments - verify(spyAppPairsController, never()).launchAppPair(any()) + verify(spyAppPairsController, never()).launchAppPair(any(), any()) verify(spyAppPairsController, times(1)) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT)) } @@ -330,7 +330,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchAppPair was called - verify(spyAppPairsController, times(1)).launchAppPair(any()) + verify(spyAppPairsController, times(1)).launchAppPair(any(), any()) verify(spyAppPairsController, never()) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) } @@ -354,7 +354,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchToSide was called with the correct arguments - verify(spyAppPairsController, never()).launchAppPair(any()) + verify(spyAppPairsController, never()).launchAppPair(any(), any()) verify(spyAppPairsController, times(1)) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_BOTTOM_OR_RIGHT)) } @@ -378,7 +378,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchToSide was called with the correct arguments - verify(spyAppPairsController, never()).launchAppPair(any()) + verify(spyAppPairsController, never()).launchAppPair(any(), any()) verify(spyAppPairsController, times(1)) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT)) } @@ -402,7 +402,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchAppPair was called - verify(spyAppPairsController, times(1)).launchAppPair(any()) + verify(spyAppPairsController, times(1)).launchAppPair(any(), any()) verify(spyAppPairsController, never()) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) } diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index e861d38733..30fa7ffbc0 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -22,6 +22,8 @@ import android.view.KeyEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; +import androidx.annotation.Nullable; + import com.android.launcher3.BubbleTextView; import com.android.launcher3.ButtonDropTarget; import com.android.launcher3.CellLayout; @@ -58,6 +60,7 @@ import com.android.launcher3.widget.util.WidgetSizes; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.Consumer; public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate { @@ -173,7 +176,7 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate finishCallback) { final int[] coordinates = new int[2]; final int screenId = findSpaceOnWorkspace(item, coordinates); if (screenId == -1) { + if (finishCallback != null) { + finishCallback.accept(false /*success*/); + } return false; } mContext.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> { @@ -400,7 +409,7 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate workspace = mContext.getWorkspace(); workspace.snapToPage(workspace.getPageIndexForScreenId(screenId)); @@ -423,23 +432,30 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate { mContext.getModelWriter().addItemToDatabase(member, fi.id, -1, -1, -1); }); - bindItem(fi, accessibility); + bindItem(fi, accessibility, finishCallback); } })); return true; } - private void bindItem(ItemInfo item, boolean focusForAccessibility) { + private void bindItem(ItemInfo item, boolean focusForAccessibility, + @Nullable Consumer finishCallback) { View view = mContext.getItemInflater().inflateItem(item, mContext.getModelWriter()); if (view == null) { + if (finishCallback != null) { + finishCallback.accept(false /*success*/); + } return; } - AnimatorSet anim = null; - if (focusForAccessibility) { - anim = new AnimatorSet(); - anim.addListener(forEndCallback( - () -> view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED))); - } + AnimatorSet anim = new AnimatorSet(); + anim.addListener(forEndCallback((success) -> { + if (focusForAccessibility) { + view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); + } + if (finishCallback != null) { + finishCallback.accept(success); + } + })); mContext.bindInflatedItems(Collections.singletonList(Pair.create(item, view)), anim); } From 16b68816ceb5f379de57895461a4fc41e0c88447 Mon Sep 17 00:00:00 2001 From: Andrew Cole Date: Fri, 15 Mar 2024 14:16:45 -0700 Subject: [PATCH 4/9] Moving FORCE_MONOCHROME_APP_ICONS to New Flag System In order to continue work on theming all app icons we are moving the developer flag over. This is cleanup work Bug: 270396209 Test: None Flag: ACONFIG FLAG_FORCE_MONOCHROME_APP_ICONS disabled Change-Id: Ia290a6761aec68eadd2e78f64ac5dbd233af6033 --- aconfig/launcher.aconfig | 7 +++++++ src/com/android/launcher3/config/FeatureFlags.java | 5 ----- src/com/android/launcher3/icons/LauncherIcons.java | 5 ++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig index 462d947a24..ec0953cd85 100644 --- a/aconfig/launcher.aconfig +++ b/aconfig/launcher.aconfig @@ -170,6 +170,13 @@ flag { bug: "318410881" } +flag { + name: "force_monochrome_app_icons" + namespace: "launcher" + description: "Enable the ability to generate monochromatic icons, if it is not provided by the app" + bug: "270396209" +} + flag { name: "enable_add_app_widget_via_config_activity_v2" namespace: "launcher" diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index e25e0338af..2d6fdc8047 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -184,11 +184,6 @@ public final class FeatureFlags { "SECONDARY_DRAG_N_DROP_TO_PIN", DISABLED, "Enable dragging and dropping to pin apps within secondary display"); - // TODO(Block 7): Clean up flags - public static final BooleanFlag ENABLE_FORCED_MONO_ICON = getDebugFlag(270396209, - "ENABLE_FORCED_MONO_ICON", DISABLED, - "Enable the ability to generate monochromatic icons, if it is not provided by the app"); - // TODO(Block 8): Clean up flags // TODO(Block 9): Clean up flags diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java index a15348bb57..513377ae61 100644 --- a/src/com/android/launcher3/icons/LauncherIcons.java +++ b/src/com/android/launcher3/icons/LauncherIcons.java @@ -16,14 +16,13 @@ package com.android.launcher3.icons; -import static com.android.launcher3.config.FeatureFlags.ENABLE_FORCED_MONO_ICON; - import android.content.Context; import android.graphics.drawable.Drawable; import android.os.UserHandle; import androidx.annotation.NonNull; +import com.android.launcher3.Flags; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.graphics.IconShape; import com.android.launcher3.graphics.LauncherPreviewRenderer; @@ -103,7 +102,7 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable { @Override protected Drawable getMonochromeDrawable(Drawable base) { Drawable mono = super.getMonochromeDrawable(base); - if (mono != null || !ENABLE_FORCED_MONO_ICON.get()) { + if (mono != null || !Flags.forceMonochromeAppIcons()) { return mono; } if (mMonochromeIconFactory == null) { From 435084cf0d4afa495e6bb834a3d83dcbd1aa7f9a Mon Sep 17 00:00:00 2001 From: Federico Baron Date: Fri, 15 Mar 2024 22:58:06 +0000 Subject: [PATCH 5/9] Revert^2 "Add screenrecord for testOverviewDeadzones" This reverts commit 4d1fe6b85465fabc02a403c6807f30a021adf63d. Reason for revert: issue started occurring again Change-Id: I5a5500c42209afc6b54f7dff60a821093d45294a --- .../tests/src/com/android/quickstep/TaplTestsQuickstep.java | 1 + 1 file changed, 1 insertion(+) diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java index 360d1a7cef..8a76d124f1 100644 --- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java @@ -476,6 +476,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest { @Test @PortraitLandscape + @ScreenRecord // b/326839375 public void testOverviewDeadzones() throws Exception { startTestAppsWithCheck(); From e2625dce7bd5ad8355662347c284b3936f68b7df Mon Sep 17 00:00:00 2001 From: Uwais Ashraf Date: Mon, 18 Mar 2024 08:59:31 +0000 Subject: [PATCH 6/9] Adding coroutines library Fix: 329045417 Test: Presubmit builds passing Flag: NA Change-Id: Ifd7f4892f68f99d700d586e78ddd102aa28ff7a0 --- Android.bp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Android.bp b/Android.bp index 78db013d3a..cdada0a87f 100644 --- a/Android.bp +++ b/Android.bp @@ -201,6 +201,8 @@ android_library { "Launcher3ResLib", "launcher-testing-shared", "animationlib", + "kotlinx_coroutines_android", + "kotlinx_coroutines", "com_android_launcher3_flags_lib", "com_android_wm_shell_flags_lib", "android.appwidget.flags-aconfig-java", From 3f46a3d5627eb59c46cc572fd97cc08da31987d2 Mon Sep 17 00:00:00 2001 From: Graciela Wissen Putri Date: Thu, 16 Nov 2023 13:27:28 +0000 Subject: [PATCH 7/9] Unpin desktop task in overview Desktop task should behave like any other task in overview and should not show up as a large tile. Bug: 309008406 Flag: ACONFIG com.android.window.flags.enable_desktop_windowing_mode DEVELOPMENT Test: Swipe fullscreen task into overview Open overview from home Swipe freeform task into overview Test: Image test will be added in b/319237536 Change-Id: Iefca85747de7595357813d5ed999df0e4b51120a --- .../quickstep/BaseActivityInterface.java | 7 -- .../android/quickstep/views/RecentsView.java | 82 ++----------------- .../com/android/quickstep/views/TaskView.java | 7 +- 3 files changed, 10 insertions(+), 86 deletions(-) diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index 2341e4ccbd..59302b7afa 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -379,13 +379,6 @@ public abstract class BaseActivityInterface 0) { newFocusedTaskView = getTaskViewAt(0); - // Check if the first task is the desktop. - // If first task is desktop, try to find another task to set as the focused task - if (newFocusedTaskView != null && newFocusedTaskView.isDesktopTask() - && getTaskViewCount() > 1) { - newFocusedTaskView = getTaskViewAt(1); - } } mFocusedTaskViewId = newFocusedTaskView != null && !enableGridOnlyOverview() ? newFocusedTaskView.getTaskViewId() : INVALID_TASK_ID; @@ -1869,12 +1839,7 @@ public abstract class RecentsView 0) { - TaskView taskView = requireTaskViewAt(0); - // If first task id desktop, try to find another task to set the target page - if (taskView.isDesktopTask() && getTaskViewCount() > 1) { - taskView = requireTaskViewAt(1); - } - targetPage = indexOfChild(taskView); + targetPage = indexOfChild(requireTaskViewAt(0)); } } if (targetPage != -1 && mCurrentPage != targetPage) { @@ -2119,9 +2084,6 @@ public abstract class RecentsView focusedTaskIndex) { // For tasks after the focused task, shift by focused task's width and spacing. @@ -3060,7 +3000,7 @@ public abstract class RecentsView= 0; j--) { - if (j == focusedTaskIndex || j == desktopTaskIndex) { + if (j == focusedTaskIndex) { continue; } widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing; @@ -3079,7 +3019,7 @@ public abstract class RecentsView= 0; j--) { - if (j == focusedTaskIndex || j == desktopTaskIndex) { + if (j == focusedTaskIndex) { continue; } widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing; @@ -5545,10 +5485,6 @@ public abstract class RecentsView Date: Fri, 15 Mar 2024 18:57:11 -0400 Subject: [PATCH 8/9] Asynchronously call snapToPage so Talkback correctly scrolls to added widget Bug: 326269165 Test: locally tested via Talkback Flag: N/A Change-Id: I2be6e9758617e4eca18b3ea1ebed8189145c4108 --- .../accessibility/LauncherAccessibilityDelegate.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index e861d38733..f130b89be2 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -405,7 +405,9 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate workspace = mContext.getWorkspace(); - workspace.snapToPage(workspace.getPageIndexForScreenId(screenId)); + workspace.post( + () -> workspace.snapToPage(workspace.getPageIndexForScreenId(screenId)) + ); mContext.addPendingItem(info, LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, coordinates, info.spanX, info.spanY); } else if (item instanceof WorkspaceItemInfo) { From d8d4fb4375da10b1ca033869907eae2e08793044 Mon Sep 17 00:00:00 2001 From: Liana Kazanova Date: Mon, 18 Mar 2024 18:34:44 +0000 Subject: [PATCH 9/9] Revert "Add CUJ Jank interactions for App Pair saving and launching" Revert submission 26567492-appPairsCUJ Reason for revert: http://b/330185095 Reverted changes: /q/submissionid:26567492-appPairsCUJ Change-Id: I73d29fe3618a55622e0daa22c9960e3c5783c275 --- .../taskbar/TaskbarActivityContext.java | 2 +- .../uioverrides/QuickstepLauncher.java | 4 +- .../quickstep/util/AppPairsController.java | 26 ++---------- .../util/SplitSelectStateController.java | 16 -------- .../quickstep/util/AppPairsControllerTest.kt | 20 +++++----- .../LauncherAccessibilityDelegate.java | 40 ++++++------------- 6 files changed, 28 insertions(+), 80 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index a980af5ac1..27a895c3d4 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -1249,7 +1249,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext { if (findExactPairMatch) { // We did not find the app pair we were looking for, so launch one. recents.getSplitSelectController().getAppPairsController().launchAppPair( - (AppPairIcon) launchingIconView, -1 /*cuj*/); + (AppPairIcon) launchingIconView); } else { startItemInfoActivity(itemInfos.get(0)); } diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index ea0724662c..2c45129b8b 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -21,7 +21,6 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEAS import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED; import static com.android.app.animation.Interpolators.EMPHASIZED; -import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE; import static com.android.launcher3.Flags.enablePredictiveBackGesture; import static com.android.launcher3.Flags.enableUnfoldStateAnimation; import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO; @@ -1347,8 +1346,7 @@ public class QuickstepLauncher extends Launcher { * Launches two apps as an app pair. */ public void launchAppPair(AppPairIcon appPairIcon) { - mSplitSelectStateController.getAppPairsController().launchAppPair(appPairIcon, - CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE); + mSplitSelectStateController.getAppPairsController().launchAppPair(appPairIcon); } public boolean canStartHomeSafely() { diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java index ecb6118e46..757f1f809b 100644 --- a/quickstep/src/com/android/quickstep/util/AppPairsController.java +++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java @@ -19,7 +19,6 @@ package com.android.quickstep.util; import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR; 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; @@ -41,7 +40,6 @@ import android.util.Pair; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import com.android.internal.jank.Cuj; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; @@ -64,7 +62,6 @@ import com.android.quickstep.TopTaskTracker; import com.android.quickstep.views.GroupedTaskView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.system.InteractionJankMonitorWrapper; import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition; import java.util.Arrays; @@ -115,7 +112,6 @@ public class AppPairsController { * well on trampoline apps). */ public void saveAppPair(GroupedTaskView gtv) { - InteractionJankMonitorWrapper.begin(gtv, Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR); TaskView.TaskIdAttributeContainer[] attributes = gtv.getTaskIdAttributeContainers(); WorkspaceItemInfo recentsInfo1 = attributes[0].getItemInfo(); WorkspaceItemInfo recentsInfo2 = attributes[1].getItemInfo(); @@ -172,13 +168,7 @@ public class AppPairsController { LauncherAccessibilityDelegate delegate = Launcher.getLauncher(mContext).getAccessibilityDelegate(); if (delegate != null) { - delegate.addToWorkspace(newAppPair, true, (success) -> { - if (success) { - InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR); - } else { - InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR); - } - }); + delegate.addToWorkspace(newAppPair, true); mStatsLogManager.logger().withItemInfo(newAppPair) .log(StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_SAVE); } @@ -189,18 +179,12 @@ public class AppPairsController { /** * Launches an app pair by searching the RecentsModel for running instances of each app, and * staging either those running instances or launching the apps as new Intents. - * - * @param cuj Should be an integer from {@link Cuj} or -1 if no CUJ needs to be logged for jank - * monitoring */ - public void launchAppPair(AppPairIcon appPairIcon, int cuj) { + public void launchAppPair(AppPairIcon appPairIcon) { WorkspaceItemInfo app1 = appPairIcon.getInfo().contents.get(0); WorkspaceItemInfo app2 = appPairIcon.getInfo().contents.get(1); ComponentKey app1Key = new ComponentKey(app1.getTargetComponent(), app1.user); ComponentKey app2Key = new ComponentKey(app2.getTargetComponent(), app2.user); - mSplitSelectStateController.setLaunchingCuj(cuj); - InteractionJankMonitorWrapper.begin(appPairIcon, cuj); - mSplitSelectStateController.findLastActiveTasksAndRunCallback( Arrays.asList(app1Key, app2Key), false /* findExactPairMatch */, @@ -359,8 +343,7 @@ public class AppPairsController { && !lastActiveTasksOfAppPair.contains(runningTaskId2)) { // Neither A nor B are on screen, so just launch a new app pair // normally. - launchAppPair(launchingIconView, - CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR); + launchAppPair(launchingIconView); } else { // Exactly one app (A or B) is on-screen, so we have to launch the other // on the appropriate side. @@ -405,8 +388,7 @@ public class AppPairsController { if (!task1IsOnScreen && !task2IsOnScreen) { // Neither App A nor App B are on-screen, launch the app pair normally. - launchAppPair(launchingIconView, - CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR); + launchAppPair(launchingIconView); } else { // Either A or B is on-screen, so launch the other on the appropriate // side. diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index 44da8b13cc..8e2520e2c4 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -104,7 +104,6 @@ import com.android.quickstep.views.SplitInstructionsView; import com.android.systemui.animation.RemoteAnimationRunnerCompat; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.InteractionJankMonitorWrapper; import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition; import com.android.wm.shell.splitscreen.ISplitSelectListener; @@ -152,12 +151,6 @@ public class SplitSelectStateController { /** True when the first selected split app is being launched in fullscreen. */ private boolean mLaunchingFirstAppFullscreen; - /** - * Should be a constant from {@link com.android.internal.jank.Cuj} or -1, does not need to be - * set for all launches. - */ - private int mLaunchCuj = -1; - private FloatingTaskView mFirstFloatingTaskView; private SplitInstructionsView mSplitInstructionsView; @@ -714,10 +707,6 @@ public class SplitSelectStateController { return mSplitAnimationController; } - public void setLaunchingCuj(int launchCuj) { - mLaunchCuj = launchCuj; - } - /** * Requires Shell Transitions */ @@ -861,11 +850,6 @@ public class SplitSelectStateController { mSplitInstructionsView = null; mLaunchingFirstAppFullscreen = false; - if (mLaunchCuj != -1) { - InteractionJankMonitorWrapper.end(mLaunchCuj); - } - mLaunchCuj = -1; - if (mSessionInstanceIds != null) { mStatsLogManager.logger() .withInstanceId(mSessionInstanceIds.second) diff --git a/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt index adaf7ff8c3..510faf6132 100644 --- a/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt +++ b/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt @@ -105,7 +105,7 @@ class AppPairsControllerTest { whenever(mockTopTaskTracker.getCachedTopTask(any())).thenReturn(mockCachedTaskInfo) whenever(mockTask1.getKey()).thenReturn(mockTaskKey1) whenever(mockTask2.getKey()).thenReturn(mockTaskKey2) - doNothing().whenever(spyAppPairsController).launchAppPair(any(), any()) + doNothing().whenever(spyAppPairsController).launchAppPair(any()) doNothing() .whenever(spyAppPairsController) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) @@ -210,7 +210,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchAppPair and launchToSide were never called - verify(spyAppPairsController, never()).launchAppPair(any(), any()) + verify(spyAppPairsController, never()).launchAppPair(any()) verify(spyAppPairsController, never()) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) } @@ -234,7 +234,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchToSide was called with the correct arguments - verify(spyAppPairsController, never()).launchAppPair(any(), any()) + verify(spyAppPairsController, never()).launchAppPair(any()) verify(spyAppPairsController, times(1)) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_BOTTOM_OR_RIGHT)) } @@ -258,7 +258,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchToSide was called with the correct arguments - verify(spyAppPairsController, never()).launchAppPair(any(), any()) + verify(spyAppPairsController, never()).launchAppPair(any()) verify(spyAppPairsController, times(1)) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT)) } @@ -282,7 +282,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchToSide was called with the correct arguments - verify(spyAppPairsController, never()).launchAppPair(any(), any()) + verify(spyAppPairsController, never()).launchAppPair(any()) verify(spyAppPairsController, times(1)) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_BOTTOM_OR_RIGHT)) } @@ -306,7 +306,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchToSide was called with the correct arguments - verify(spyAppPairsController, never()).launchAppPair(any(), any()) + verify(spyAppPairsController, never()).launchAppPair(any()) verify(spyAppPairsController, times(1)) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT)) } @@ -330,7 +330,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchAppPair was called - verify(spyAppPairsController, times(1)).launchAppPair(any(), any()) + verify(spyAppPairsController, times(1)).launchAppPair(any()) verify(spyAppPairsController, never()) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) } @@ -354,7 +354,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchToSide was called with the correct arguments - verify(spyAppPairsController, never()).launchAppPair(any(), any()) + verify(spyAppPairsController, never()).launchAppPair(any()) verify(spyAppPairsController, times(1)) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_BOTTOM_OR_RIGHT)) } @@ -378,7 +378,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchToSide was called with the correct arguments - verify(spyAppPairsController, never()).launchAppPair(any(), any()) + verify(spyAppPairsController, never()).launchAppPair(any()) verify(spyAppPairsController, times(1)) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT)) } @@ -402,7 +402,7 @@ class AppPairsControllerTest { callback.accept(arrayOf(mockTask1, mockTask2)) // Verify that launchAppPair was called - verify(spyAppPairsController, times(1)).launchAppPair(any(), any()) + verify(spyAppPairsController, times(1)).launchAppPair(any()) verify(spyAppPairsController, never()) .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) } diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index 30fa7ffbc0..e861d38733 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -22,8 +22,6 @@ import android.view.KeyEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; -import androidx.annotation.Nullable; - import com.android.launcher3.BubbleTextView; import com.android.launcher3.ButtonDropTarget; import com.android.launcher3.CellLayout; @@ -60,7 +58,6 @@ import com.android.launcher3.widget.util.WidgetSizes; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.function.Consumer; public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate { @@ -176,7 +173,7 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate finishCallback) { + public boolean addToWorkspace(ItemInfo item, boolean accessibility) { final int[] coordinates = new int[2]; final int screenId = findSpaceOnWorkspace(item, coordinates); if (screenId == -1) { - if (finishCallback != null) { - finishCallback.accept(false /*success*/); - } return false; } mContext.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> { @@ -409,7 +400,7 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate workspace = mContext.getWorkspace(); workspace.snapToPage(workspace.getPageIndexForScreenId(screenId)); @@ -432,30 +423,23 @@ public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate { mContext.getModelWriter().addItemToDatabase(member, fi.id, -1, -1, -1); }); - bindItem(fi, accessibility, finishCallback); + bindItem(fi, accessibility); } })); return true; } - private void bindItem(ItemInfo item, boolean focusForAccessibility, - @Nullable Consumer finishCallback) { + private void bindItem(ItemInfo item, boolean focusForAccessibility) { View view = mContext.getItemInflater().inflateItem(item, mContext.getModelWriter()); if (view == null) { - if (finishCallback != null) { - finishCallback.accept(false /*success*/); - } return; } - AnimatorSet anim = new AnimatorSet(); - anim.addListener(forEndCallback((success) -> { - if (focusForAccessibility) { - view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); - } - if (finishCallback != null) { - finishCallback.accept(success); - } - })); + AnimatorSet anim = null; + if (focusForAccessibility) { + anim = new AnimatorSet(); + anim.addListener(forEndCallback( + () -> view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED))); + } mContext.bindInflatedItems(Collections.singletonList(Pair.create(item, view)), anim); }