diff --git a/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml b/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml index 98aab67987..d722dd7f94 100644 --- a/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml +++ b/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml @@ -13,12 +13,15 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - - - - + android:inset="@dimen/bubble_expanded_view_drop_target_padding"> + + + + + + diff --git a/quickstep/res/layout/bubble_expanded_view_drop_target.xml b/quickstep/res/layout/bubble_expanded_view_drop_target.xml index 15ec49a6b0..3bd5d31dd5 100644 --- a/quickstep/res/layout/bubble_expanded_view_drop_target.xml +++ b/quickstep/res/layout/bubble_expanded_view_drop_target.xml @@ -16,8 +16,8 @@ \ No newline at end of file diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml index b3502db6ed..fd122103e1 100644 --- a/quickstep/res/values/config.xml +++ b/quickstep/res/values/config.xml @@ -17,8 +17,7 @@ - - + com.android.launcher3/com.android.quickstep.interaction.GestureSandboxActivity diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index c5f25ad87e..aea460247e 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -456,8 +456,10 @@ 36dp - 16dp - 412dp + 412dp + 600dp + 28dp + 24dp 16dp diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 0697f47cda..72aaa9000a 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -58,6 +58,7 @@ import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVIT import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; import static com.android.launcher3.testing.shared.TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE; import static com.android.launcher3.util.DisplayController.isTransientTaskbar; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR; import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE; import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs; @@ -1881,7 +1882,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener return new ContainerAnimationRunner( new ActivityTransitionAnimator.AnimationDelegate( - controller, callback, listener)); + MAIN_EXECUTOR, controller, callback, listener)); } /** diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java index 70e01f53a0..a16031d804 100644 --- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java +++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java @@ -286,7 +286,7 @@ public class PredictionRowView } public void dump(String prefix, PrintWriter writer) { - writer.println(prefix + this.getClass().getSimpleName()); + writer.println(prefix + "PredictionRowView"); writer.println(prefix + "\tmPredictionsEnabled: " + mPredictionsEnabled); writer.println(prefix + "\tmPredictionUiUpdatePaused: " + mPredictionUiUpdatePaused); writer.println(prefix + "\tmNumPredictedAppsPerRow: " + mNumPredictedAppsPerRow); diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index 1c5a75dd1c..de974ecae1 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -29,6 +29,7 @@ import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.ComponentName; +import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.View; import android.view.ViewGroup; @@ -80,6 +81,7 @@ public class HotseatPredictionController implements DragController.DragListener, SystemShortcut.Factory, DeviceProfile.OnDeviceProfileChangeListener, DragSource, ViewGroup.OnHierarchyChangeListener { + private static final String TAG = "HotseatPredictionController"; private static final int FLAG_UPDATE_PAUSED = 1 << 0; private static final int FLAG_DRAG_IN_PROGRESS = 1 << 1; private static final int FLAG_FILL_IN_PROGRESS = 1 << 2; @@ -291,6 +293,16 @@ public class HotseatPredictionController implements DragController.DragListener, } } + /** + * Ensures that if the flag FLAG_UPDATE_PAUSED is active we set it to false. + */ + public void verifyUIUpdateNotPaused() { + if ((mPauseFlags & FLAG_UPDATE_PAUSED) != 0) { + setPauseUIUpdate(false); + Log.e(TAG, "FLAG_UPDATE_PAUSED should not be set to true (see b/339700174)"); + } + } + /** * Sets or updates the predicted items */ @@ -521,7 +533,7 @@ public class HotseatPredictionController implements DragController.DragListener, } public void dump(String prefix, PrintWriter writer) { - writer.println(prefix + this.getClass().getSimpleName()); + writer.println(prefix + "HotseatPredictionController"); writer.println(prefix + "\tFlags: " + getStateString(mPauseFlags)); writer.println(prefix + "\tmHotSeatItemsCount: " + mHotSeatItemsCount); writer.println(prefix + "\tmPredictedItems: " + mPredictedItems); diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java index 65a49bd7c9..8b5ed7cc4b 100644 --- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java +++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java @@ -73,6 +73,7 @@ import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.ApiWrapper; import com.android.launcher3.util.Executors; import com.android.launcher3.util.IntSparseArrayMap; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PersistedItemArray; import com.android.quickstep.logging.SettingsChangeLogger; import com.android.quickstep.logging.StatsLogCompatManager; @@ -150,7 +151,8 @@ public class QuickstepModelDelegate extends ModelDelegate { // TODO: Implement caching and preloading WorkspaceItemFactory factory = - new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, numColumns, state.containerId); + new WorkspaceItemFactory(mApp, ums, mPmHelper, pinnedShortcuts, numColumns, + state.containerId); FixedContainerItems fci = new FixedContainerItems(state.containerId, state.storage.read(mApp.getContext(), factory, ums.allUsers::get)); if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) { @@ -530,6 +532,7 @@ public class QuickstepModelDelegate extends ModelDelegate { private final LauncherAppState mAppState; private final UserManagerState mUMS; + private final PackageManagerHelper mPmHelper; private final Map mPinnedShortcuts; private final int mMaxCount; private final int mContainer; @@ -537,9 +540,11 @@ public class QuickstepModelDelegate extends ModelDelegate { private int mReadCount = 0; protected WorkspaceItemFactory(LauncherAppState appState, UserManagerState ums, - Map pinnedShortcuts, int maxCount, int container) { + PackageManagerHelper pmHelper, Map pinnedShortcuts, + int maxCount, int container) { mAppState = appState; mUMS = ums; + mPmHelper = pmHelper; mPinnedShortcuts = pinnedShortcuts; mMaxCount = maxCount; mContainer = container; @@ -563,6 +568,7 @@ public class QuickstepModelDelegate extends ModelDelegate { lai, UserCache.INSTANCE.get(mAppState.getContext()).getUserInfo(user), ApiWrapper.INSTANCE.get(mAppState.getContext()), + mPmHelper, mUMS.isUserQuiet(user)); info.container = mContainer; mAppState.getIconCache().getTitleAndIcon(info, lai, false); diff --git a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java index 184ea717cd..fe9ade9896 100644 --- a/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java +++ b/quickstep/src/com/android/launcher3/popup/QuickstepSystemShortcut.java @@ -25,7 +25,7 @@ import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; /** {@link SystemShortcut.Factory} implementation to create workspace split shortcuts */ public interface QuickstepSystemShortcut { - String TAG = QuickstepSystemShortcut.class.getSimpleName(); + String TAG = "QuickstepSystemShortcut"; static SystemShortcut.Factory getSplitSelectShortcutByPosition( SplitPositionOption position) { diff --git a/quickstep/src/com/android/launcher3/splitscreen/SplitShortcut.kt b/quickstep/src/com/android/launcher3/splitscreen/SplitShortcut.kt index 2b6f77f231..c94edceb43 100644 --- a/quickstep/src/com/android/launcher3/splitscreen/SplitShortcut.kt +++ b/quickstep/src/com/android/launcher3/splitscreen/SplitShortcut.kt @@ -45,7 +45,7 @@ abstract class SplitShortcut( ) : SystemShortcut(iconResId, labelResId, target, itemInfo, originalView) where T : Context?, T : ActivityContext? { - private val TAG = SystemShortcut::class.java.simpleName + private val TAG = "SplitShortcut" // Initiate splitscreen from the Home screen or Home All Apps protected val splitSelectSource: SplitSelectSource? diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java index 882682dc4a..747612d823 100644 --- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java +++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java @@ -182,7 +182,7 @@ public class DepthController extends BaseDepthController implements StateHandler } public void dump(String prefix, PrintWriter writer) { - writer.println(prefix + this.getClass().getSimpleName()); + writer.println(prefix + "DepthController"); writer.println(prefix + "\tmMaxBlurRadius=" + mMaxBlurRadius); writer.println(prefix + "\tmCrossWindowBlursEnabled=" + mCrossWindowBlursEnabled); writer.println(prefix + "\tmSurface=" + mSurface); diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java index c0ecc6151a..06d9ee6c96 100644 --- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java @@ -133,4 +133,9 @@ public class FallbackTaskbarUIController extends TaskbarUIController { protected TISBindHelper getTISBindHelper() { return mRecentsActivity.getTISBindHelper(); } + + @Override + protected String getTaskbarUIControllerName() { + return "FallbackTaskbarUIController"; + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 2ce6a41cb9..4f021224c3 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -446,4 +446,9 @@ public class LauncherTaskbarUIController extends TaskbarUIController { mTaskbarLauncherStateController.dumpLogs(prefix + "\t", pw); } + + @Override + protected String getTaskbarUIControllerName() { + return "LauncherTaskbarUIController"; + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 0b0df95c68..af053e3a4a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -260,9 +260,9 @@ public class TaskbarActivityContext extends BaseTaskbarContext { new BubbleDragController(this), new BubbleDismissController(this, mDragLayer), new BubbleBarPinController(this, mDragLayer, - () -> getDeviceProfile().getDisplayInfo().currentSize), + () -> DisplayController.INSTANCE.get(this).getInfo().currentSize), new BubblePinController(this, mDragLayer, - () -> getDeviceProfile().getDisplayInfo().currentSize) + () -> DisplayController.INSTANCE.get(this).getInfo().currentSize) )); } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java index ee21eacb8c..6ea52cb888 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java @@ -65,7 +65,7 @@ import java.util.StringJoiner; */ public class TaskbarLauncherStateController { - private static final String TAG = TaskbarLauncherStateController.class.getSimpleName(); + private static final String TAG = "TaskbarLauncherStateController"; private static final boolean DEBUG = false; /** Launcher activity is visible and focused. */ diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java index ade4649dab..13a68a04a7 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java @@ -66,7 +66,7 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa /** Allow some time in between the long press for back and recents. */ static final int SCREEN_PIN_LONG_PRESS_THRESHOLD = 200; static final int SCREEN_PIN_LONG_PRESS_RESET = SCREEN_PIN_LONG_PRESS_THRESHOLD + 100; - private static final String TAG = TaskbarNavButtonController.class.getSimpleName(); + private static final String TAG = "TaskbarNavButtonController"; private long mLastScreenPinLongPress; private boolean mScreenPinned; diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java index 4da77627f7..f764a835f6 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java @@ -78,7 +78,7 @@ import java.util.function.IntPredicate; * create a cohesive animation between stashed/unstashed states. */ public class TaskbarStashController implements TaskbarControllers.LoggableTaskbarController { - private static final String TAG = TaskbarStashController.class.getSimpleName(); + private static final String TAG = "TaskbarStashController"; private static final boolean DEBUG = false; public static final int FLAG_IN_APP = 1 << 0; diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java index 32e4097de4..6abd5a9c75 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java @@ -55,7 +55,6 @@ import java.util.stream.Stream; * Base class for providing different taskbar UI */ public class TaskbarUIController { - public static final TaskbarUIController DEFAULT = new TaskbarUIController(); // Initialized in init. @@ -91,6 +90,10 @@ public class TaskbarUIController { */ protected void onIconLayoutBoundsChanged() { } + protected String getTaskbarUIControllerName() { + return "TaskbarUIController"; + } + /** Called when an icon is launched. */ @CallSuper public void onTaskbarIconLaunched(ItemInfo item) { @@ -207,7 +210,7 @@ public class TaskbarUIController { pw.println(String.format( "%sTaskbarUIController: using an instance of %s", prefix, - getClass().getSimpleName())); + getTaskbarUIControllerName())); } /** diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index 77f8a8a088..df7a7ba84a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -71,7 +71,7 @@ import java.util.function.Predicate; */ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable, DeviceProfile.OnDeviceProfileChangeListener { - private static final String TAG = TaskbarView.class.getSimpleName(); + private static final String TAG = "TaskbarView"; private static final Rect sTmpRect = new Rect(); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index e0b446e5a9..0ee3d7f361 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -79,7 +79,7 @@ import java.util.function.Predicate; */ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbarController { - private static final String TAG = TaskbarViewController.class.getSimpleName(); + private static final String TAG = "TaskbarViewController"; private static final Runnable NO_OP = () -> { }; diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java index 8c83508ad9..5789f0c611 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java @@ -94,7 +94,7 @@ import java.util.concurrent.Executors; */ public class BubbleBarController extends IBubblesListener.Stub { - private static final String TAG = BubbleBarController.class.getSimpleName(); + private static final String TAG = "BubbleBarController"; private static final boolean DEBUG = false; /** @@ -150,6 +150,7 @@ public class BubbleBarController extends IBubblesListener.Stub { private BubbleBarViewController mBubbleBarViewController; private BubbleStashController mBubbleStashController; private BubbleStashedHandleViewController mBubbleStashedHandleViewController; + private BubblePinController mBubblePinController; // Keep track of bubble bar bounds sent to shell to avoid sending duplicate updates private final Rect mLastSentBubbleBarBounds = new Rect(); @@ -169,6 +170,7 @@ public class BubbleBarController extends IBubblesListener.Stub { BubbleBarLocation bubbleBarLocation; List removedBubbles; List bubbleKeysInOrder; + Point expandedViewDropTargetSize; // These need to be loaded in the background BubbleBarBubble addedBubble; @@ -186,6 +188,7 @@ public class BubbleBarController extends IBubblesListener.Stub { bubbleBarLocation = update.bubbleBarLocation; removedBubbles = update.removedBubbles; bubbleKeysInOrder = update.bubbleKeysInOrder; + expandedViewDropTargetSize = update.expandedViewDropTargetSize; } } @@ -216,6 +219,7 @@ public class BubbleBarController extends IBubblesListener.Stub { mBubbleBarViewController = bubbleControllers.bubbleBarViewController; mBubbleStashController = bubbleControllers.bubbleStashController; mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController; + mBubblePinController = bubbleControllers.bubblePinController; bubbleControllers.runAfterInit(() -> { mBubbleBarViewController.setHiddenForBubbles( @@ -419,6 +423,9 @@ public class BubbleBarController extends IBubblesListener.Stub { updateBubbleBarLocationInternal(update.bubbleBarLocation); } } + if (update.expandedViewDropTargetSize != null) { + mBubblePinController.setDropTargetSize(update.expandedViewDropTargetSize); + } } /** Tells WMShell to show the currently selected bubble. */ diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java index 4334807021..d08015e0ca 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java @@ -76,7 +76,7 @@ import java.util.function.Consumer; */ public class BubbleBarView extends FrameLayout { - private static final String TAG = BubbleBarView.class.getSimpleName(); + private static final String TAG = "BubbleBarView"; // TODO: (b/273594744) calculate the amount of space we have and base the max on that // if it's smaller than 5. diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java index ac02f1fe5e..cd8eaf9a5e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java @@ -55,7 +55,7 @@ import java.util.function.Consumer; */ public class BubbleBarViewController { - private static final String TAG = BubbleBarViewController.class.getSimpleName(); + private static final String TAG = "BubbleBarViewController"; private static final float APP_ICON_SMALL_DP = 44f; private static final float APP_ICON_MEDIUM_DP = 48f; private static final float APP_ICON_LARGE_DP = 52f; diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java index a40f33c595..0e6fa3c5e8 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java @@ -40,7 +40,7 @@ import com.android.wm.shell.common.magnetictarget.MagnetizedObject; * @see BubbleDragController */ public class BubbleDismissController { - private static final String TAG = BubbleDismissController.class.getSimpleName(); + private static final String TAG = "BubbleDismissController"; private static final float FLING_TO_DISMISS_MIN_VELOCITY = 6000f; private final TaskbarActivityContext mActivity; private final TaskbarDragLayer mDragLayer; diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt index fef7fa1dd5..a77e685d00 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt @@ -37,12 +37,17 @@ class BubblePinController( screenSizeProvider: () -> Point ) : BaseBubblePinController(screenSizeProvider) { + var dropTargetSize: Point? = null + private lateinit var bubbleBarViewController: BubbleBarViewController private lateinit var bubbleStashController: BubbleStashController private var exclRectWidth: Float = 0f private var exclRectHeight: Float = 0f private var dropTargetView: View? = null + // Fallback width and height in case shell has not sent the size over + private var dropTargetDefaultWidth: Int = 0 + private var dropTargetDefaultHeight: Int = 0 private var dropTargetMargin: Int = 0 fun init(bubbleControllers: BubbleControllers) { @@ -50,6 +55,14 @@ class BubblePinController( bubbleStashController = bubbleControllers.bubbleStashController exclRectWidth = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_width) exclRectHeight = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_height) + dropTargetDefaultWidth = + context.resources.getDimensionPixelSize( + R.dimen.bubble_expanded_view_drop_target_default_width + ) + dropTargetDefaultHeight = + context.resources.getDimensionPixelSize( + R.dimen.bubble_expanded_view_drop_target_default_height + ) dropTargetMargin = context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_drop_target_margin) } @@ -75,7 +88,6 @@ class BubblePinController( return LayoutInflater.from(context) .inflate(R.layout.bubble_expanded_view_drop_target, container, false) .also { view -> - // TODO(b/330585402): dynamic height for the drop target based on actual height dropTargetView = view container.addView(view) } @@ -88,6 +100,8 @@ class BubblePinController( val bubbleBarBounds = bubbleBarViewController.bubbleBarBounds dropTargetView?.updateLayoutParams { gravity = BOTTOM or (if (onLeft) LEFT else RIGHT) + width = dropTargetSize?.x ?: dropTargetDefaultWidth + height = dropTargetSize?.y ?: dropTargetDefaultHeight bottomMargin = -bubbleStashController.bubbleBarTranslationY.toInt() + bubbleBarBounds.height() + diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java index d0462aa3da..5d01b9bd08 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java @@ -42,7 +42,7 @@ import com.android.wm.shell.shared.animation.PhysicsAnimator; */ public class BubbleStashController { - private static final String TAG = BubbleStashController.class.getSimpleName(); + private static final String TAG = "BubbleStashController"; /** * How long to stash/unstash. diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 4184ab27cc..b1bb198dff 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -397,6 +397,12 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer return result; } + @Override + public void startBinding() { + super.startBinding(); + mHotseatPredictionController.verifyUIUpdateNotPaused(); + } + @Override protected void onActivityFlagsChanged(int changeBits) { if ((changeBits & ACTIVITY_STATE_STARTED) != 0) { diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index 0ad60b7f1e..4d3fe41f5d 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -111,7 +111,7 @@ import java.util.List; * Holds the reference to SystemUI. */ public class SystemUiProxy implements ISystemUiProxy, NavHandle, SafeCloseable { - private static final String TAG = SystemUiProxy.class.getSimpleName(); + private static final String TAG = "SystemUiProxy"; public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(SystemUiProxy::new); diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java index f052a9d580..a53d91fd7d 100644 --- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java +++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java @@ -335,24 +335,15 @@ public interface TaskShortcutFactory { recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView)); boolean shouldShowActionsButtonInstead = isLargeTileFocusedTask && isInExpectedScrollPosition; - boolean hasUnpinnableApp = taskView.getTaskContainers().stream() - .anyMatch(att -> att != null && att.getItemInfo() != null - && ((att.getItemInfo().runtimeStatusFlags - & ItemInfoWithIcon.FLAG_NOT_PINNABLE) != 0)); // No "save app pair" menu item if: - // - app pairs feature is not enabled // - we are in 3p launcher - // - the task in question is a single task - // - at least one app in app pair is unpinnable // - the Overview Actions Button should be visible - // - the task is not a GroupedTaskView - if (!FeatureFlags.enableAppPairs() - || !recentsView.supportsAppPairs() - || !taskView.containsMultipleTasks() - || hasUnpinnableApp + // - the task view is not a valid save-able split pair + if (!recentsView.supportsAppPairs() || shouldShowActionsButtonInstead - || !(taskView instanceof GroupedTaskView)) { + || !recentsView.getSplitSelectController().getAppPairsController() + .canSaveAppPair(taskView)) { return null; } diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java index 770a4525cf..e4e2eb2769 100644 --- a/quickstep/src/com/android/quickstep/util/AppPairsController.java +++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java @@ -22,6 +22,7 @@ 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.model.data.ItemInfoWithIcon.FLAG_SUPPORTS_MULTI_INSTANCE; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; @@ -32,34 +33,38 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.isPersisten import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.LauncherApps; -import android.content.pm.PackageManager; import android.util.Log; import android.util.Pair; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.jank.Cuj; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; +import com.android.launcher3.R; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.apppairs.AppPairIcon; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.IconCache; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.AppPairInfo; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.taskbar.TaskbarActivityContext; import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.SplitConfigurationOptions.StagePosition; import com.android.launcher3.views.ActivityContext; import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.TaskUtils; import com.android.quickstep.TopTaskTracker; import com.android.quickstep.views.GroupedTaskView; import com.android.quickstep.views.TaskView; @@ -100,6 +105,55 @@ public class AppPairsController { mContext = null; } + /** + * Returns whether the specified GroupedTaskView can be saved as an app pair. + */ + public boolean canSaveAppPair(TaskView taskView) { + if (mContext == null) { + // Can ignore as the activity is already destroyed + return false; + } + + // Disallow saving app pairs if: + // - app pairs feature is not enabled + // - the task in question is a single task + // - at least one app in app pair is unpinnable + // - the task is not a GroupedTaskView + // - both tasks in the GroupedTaskView are from the same app and the app does not + // support multi-instance + boolean hasUnpinnableApp = taskView.getTaskContainers().stream() + .anyMatch(att -> att != null && att.getItemInfo() != null + && ((att.getItemInfo().runtimeStatusFlags + & ItemInfoWithIcon.FLAG_NOT_PINNABLE) != 0)); + if (!FeatureFlags.enableAppPairs() + || !taskView.containsMultipleTasks() + || hasUnpinnableApp + || !(taskView instanceof GroupedTaskView)) { + return false; + } + + GroupedTaskView gtv = (GroupedTaskView) taskView; + List containers = gtv.getTaskContainers(); + ComponentKey taskKey1 = TaskUtils.getLaunchComponentKeyForTask( + containers.get(0).getTask().key); + ComponentKey taskKey2 = TaskUtils.getLaunchComponentKeyForTask( + containers.get(1).getTask().key); + AppInfo app1 = resolveAppInfoByComponent(taskKey1); + AppInfo app2 = resolveAppInfoByComponent(taskKey2); + + if (app1 == null || app2 == null) { + // Disallow saving app pairs for apps that don't have a front-door in Launcher + return false; + } + + if (PackageManagerHelper.isSameAppForMultiInstance(app1, app2)) { + if (!app1.supportsMultiInstance() || !app2.supportsMultiInstance()) { + return false; + } + } + return true; + } + /** * Creates a new app pair ItemInfo and adds it to the workspace. *
@@ -119,31 +173,23 @@ public class AppPairsController { List containers = gtv.getTaskContainers(); WorkspaceItemInfo recentsInfo1 = containers.get(0).getItemInfo(); WorkspaceItemInfo recentsInfo2 = containers.get(1).getItemInfo(); - WorkspaceItemInfo app1 = lookupLaunchableItem(recentsInfo1.getComponentKey()); - WorkspaceItemInfo app2 = lookupLaunchableItem(recentsInfo2.getComponentKey()); + WorkspaceItemInfo app1 = resolveAppPairWorkspaceInfo(recentsInfo1); + WorkspaceItemInfo app2 = resolveAppPairWorkspaceInfo(recentsInfo2); - // If app lookup fails, use the WorkspaceItemInfo that we have, but try to override default - // intent with one from PackageManager. - if (app1 == null) { - Log.w(TAG, "Creating an app pair, but app lookup for " + recentsInfo1.title - + " failed. Falling back to the WorkspaceItemInfo from Recents."); - app1 = convertRecentsItemToAppItem(recentsInfo1); + if (app1 == null || app2 == null) { + // This shouldn't happen if canSaveAppPair() is called above, but log an error and do + // not create the app pair if the workspace items can't be resolved + Log.w(TAG, "Failed to save app pair due to invalid apps (" + + "app1=" + recentsInfo1.getComponentKey().componentName + + " app2=" + recentsInfo2.getComponentKey().componentName + ")"); + return; } - if (app2 == null) { - Log.w(TAG, "Creating an app pair, but app lookup for " + recentsInfo2.title - + " failed. Falling back to the WorkspaceItemInfo from Recents."); - app2 = convertRecentsItemToAppItem(recentsInfo2); - } - - // WorkspaceItemProcessor won't process these new ItemInfos until the next launcher restart, - // so update some flags now. - updateWorkspaceItemFlags(app1); - updateWorkspaceItemFlags(app2); @PersistentSnapPosition int snapPosition = gtv.getSnapPosition(); if (!isPersistentSnapPosition(snapPosition)) { - // if we received an illegal snap position, log an error and do not create the app pair. - Log.wtf(TAG, "tried to save an app pair with illegal snapPosition " + snapPosition); + // If we received an illegal snap position, log an error and do not create the app pair + Log.wtf(TAG, "Tried to save an app pair with illegal snapPosition " + + snapPosition); return; } @@ -228,68 +274,39 @@ public class AppPairsController { ); } + /** + * Returns an AppInfo associated with the app for the given ComponentKey, or null if no such + * package exists in the AllAppsStore. + */ + @Nullable + private AppInfo resolveAppInfoByComponent(@NonNull ComponentKey key) { + AllAppsStore appsStore = ActivityContext.lookupContext(mContext) + .getAppsView().getAppsStore(); + + // First look up the app info in order of: + // - The exact activity for the recent task + // - The first(?) loaded activity from the package + AppInfo appInfo = appsStore.getApp(key); + if (appInfo == null) { + appInfo = appsStore.getApp(key, PACKAGE_KEY_COMPARATOR); + } + return appInfo; + } + /** * Creates a new launchable WorkspaceItemInfo of itemType=ITEM_TYPE_APPLICATION by looking the * ComponentKey up in the AllAppsStore. If no app is found, attempts a lookup by package * instead. If that lookup fails, returns null. */ @Nullable - private WorkspaceItemInfo lookupLaunchableItem(@Nullable ComponentKey key) { - if (key == null) { + private WorkspaceItemInfo resolveAppPairWorkspaceInfo( + @NonNull WorkspaceItemInfo recentTaskInfo) { + // ComponentKey should never be null (see TaskView#getItemInfo) + AppInfo appInfo = resolveAppInfoByComponent(recentTaskInfo.getComponentKey()); + if (appInfo == null) { return null; } - - AllAppsStore appsStore = ActivityContext.lookupContext(mContext) - .getAppsView().getAppsStore(); - - // Lookup by ComponentKey - AppInfo appInfo = appsStore.getApp(key); - if (appInfo == null) { - // Lookup by package - appInfo = appsStore.getApp(key, PACKAGE_KEY_COMPARATOR); - } - - return appInfo != null ? appInfo.makeWorkspaceItem(mContext) : null; - } - - /** - * Updates flags for newly created WorkspaceItemInfos. - */ - private void updateWorkspaceItemFlags(WorkspaceItemInfo wii) { - PackageManager pm = mContext.getPackageManager(); - ActivityInfo ai = null; - try { - ai = pm.getActivityInfo(wii.getTargetComponent(), 0); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "PackageManager lookup failed."); - } - - if (ai != null) { - wii.setNonResizeable(ai.resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE); - } - } - - /** - * Converts a WorkspaceItemInfo of itemType=ITEM_TYPE_TASK (from a Recents task) to a new - * WorkspaceItemInfo of itemType=ITEM_TYPE_APPLICATION. - */ - private WorkspaceItemInfo convertRecentsItemToAppItem(WorkspaceItemInfo recentsItem) { - if (recentsItem.itemType != LauncherSettings.Favorites.ITEM_TYPE_TASK) { - Log.w(TAG, "Expected ItemInfo of type ITEM_TYPE_TASK, but received " - + recentsItem.itemType); - } - - WorkspaceItemInfo launchableItem = recentsItem.clone(); - PackageManager p = mContext.getPackageManager(); - Intent launchIntent = p.getLaunchIntentForPackage(recentsItem.getTargetPackage()); - Log.w(TAG, "Initial intent from Recents: " + launchableItem.intent + "\n" - + "Intent from PackageManager: " + launchIntent); - if (launchIntent != null) { - // If lookup from PackageManager fails, just use the existing intent - launchableItem.intent = launchIntent; - } - launchableItem.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; - return launchableItem; + return appInfo.makeWorkspaceItem(mContext); } /** diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt index 06edb14a1f..8258ab81af 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt +++ b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt @@ -38,36 +38,40 @@ import com.android.quickstep.util.SplitSelectDataHolder.Companion.SplitLaunchTyp import java.io.PrintWriter /** - * Holds/transforms/signs/seals/delivers information for the transient state of the user - * selecting a first app to start split with and then choosing a second app. - * This class DOES NOT associate itself with drag-and-drop split screen starts because they come - * from the bad part of town. + * Holds/transforms/signs/seals/delivers information for the transient state of the user selecting a + * first app to start split with and then choosing a second app. This class DOES NOT associate + * itself with drag-and-drop split screen starts because they come from the bad part of town. * * After setting the correct fields for initial/second.* variables, this converts them into the - * correct [PendingIntent] and [ShortcutInfo] objects where applicable and sends the necessary - * data back via [getSplitLaunchData]. Note: there should be only one "initial" field and one - * "second" field set, with the rest remaining null. (Exception: [Intent] and [UserHandle] are - * always passed in together as a set, and are converted to a single [PendingIntent] or + * correct [PendingIntent] and [ShortcutInfo] objects where applicable and sends the necessary data + * back via [getSplitLaunchData]. Note: there should be only one "initial" field and one "second" + * field set, with the rest remaining null. (Exception: [Intent] and [UserHandle] are always passed + * in together as a set, and are converted to a single [PendingIntent] or * [ShortcutInfo]+[PendingIntent] before launch.) * * [SplitLaunchType] indicates the type of tasks/apps/intents being launched given the provided * state */ -class SplitSelectDataHolder( - var context: Context? -) { +class SplitSelectDataHolder(var context: Context?) { val TAG = SplitSelectDataHolder::class.simpleName /** - * Order of the constant indicates the order of which task/app was selected. - * Ex. SPLIT_TASK_SHORTCUT means primary split app identified by task, secondary is shortcut + * Order of the constant indicates the order of which task/app was selected. Ex. + * SPLIT_TASK_SHORTCUT means primary split app identified by task, secondary is shortcut * SPLIT_SHORTCUT_TASK means primary split app is determined by shortcut, secondary is task */ companion object { - @IntDef(SPLIT_TASK_TASK, SPLIT_TASK_PENDINGINTENT, SPLIT_TASK_SHORTCUT, - SPLIT_PENDINGINTENT_TASK, SPLIT_PENDINGINTENT_PENDINGINTENT, SPLIT_SHORTCUT_TASK, - SPLIT_SINGLE_TASK_FULLSCREEN, SPLIT_SINGLE_INTENT_FULLSCREEN, - SPLIT_SINGLE_SHORTCUT_FULLSCREEN) + @IntDef( + SPLIT_TASK_TASK, + SPLIT_TASK_PENDINGINTENT, + SPLIT_TASK_SHORTCUT, + SPLIT_PENDINGINTENT_TASK, + SPLIT_PENDINGINTENT_PENDINGINTENT, + SPLIT_SHORTCUT_TASK, + SPLIT_SINGLE_TASK_FULLSCREEN, + SPLIT_SINGLE_INTENT_FULLSCREEN, + SPLIT_SINGLE_SHORTCUT_FULLSCREEN + ) @Retention(AnnotationRetention.SOURCE) annotation class SplitLaunchType @@ -84,8 +88,7 @@ class SplitSelectDataHolder( const val SPLIT_SINGLE_SHORTCUT_FULLSCREEN = 8 } - @StagePosition - private var initialStagePosition: Int = STAGE_POSITION_UNDEFINED + @StagePosition private var initialStagePosition: Int = STAGE_POSITION_UNDEFINED private var itemInfo: ItemInfo? = null private var secondItemInfo: ItemInfo? = null private var splitEvent: EventEnum? = null @@ -108,12 +111,16 @@ class SplitSelectDataHolder( /** * @param alreadyRunningTask if set to [android.app.ActivityTaskManager.INVALID_TASK_ID] - * then @param intent will be used to launch the initial task + * then @param intent will be used to launch the initial task * @param intent will be ignored if @param alreadyRunningTask is set */ - fun setInitialTaskSelect(intent: Intent?, @StagePosition stagePosition: Int, - itemInfo: ItemInfo?, splitEvent: EventEnum?, - alreadyRunningTask: Int) { + fun setInitialTaskSelect( + intent: Intent?, + @StagePosition stagePosition: Int, + itemInfo: ItemInfo?, + splitEvent: EventEnum?, + alreadyRunningTask: Int + ) { if (alreadyRunningTask != INVALID_TASK_ID) { initialTaskId = alreadyRunningTask } else { @@ -127,15 +134,21 @@ class SplitSelectDataHolder( * To be called after first task selected from using a split shortcut from the fullscreen * running app. */ - fun setInitialTaskSelect(info: RunningTaskInfo, - @StagePosition stagePosition: Int, itemInfo: ItemInfo?, - splitEvent: EventEnum?) { + fun setInitialTaskSelect( + info: RunningTaskInfo, + @StagePosition stagePosition: Int, + itemInfo: ItemInfo?, + splitEvent: EventEnum? + ) { initialTaskId = info.taskId setInitialData(stagePosition, splitEvent, itemInfo) } - private fun setInitialData(@StagePosition stagePosition: Int, - event: EventEnum?, item: ItemInfo?) { + private fun setInitialData( + @StagePosition stagePosition: Int, + event: EventEnum?, + item: ItemInfo? + ) { itemInfo = item initialStagePosition = stagePosition splitEvent = event @@ -143,6 +156,7 @@ class SplitSelectDataHolder( /** * To be called as soon as user selects the second task (even if animations aren't complete) + * * @param taskId The second task that will be launched. */ fun setSecondTask(taskId: Int, itemInfo: ItemInfo) { @@ -152,6 +166,7 @@ class SplitSelectDataHolder( /** * To be called as soon as user selects the second app (even if animations aren't complete) + * * @param intent The second intent that will be launched. * @param user The user of that intent. */ @@ -162,8 +177,9 @@ class SplitSelectDataHolder( } /** - * To be called as soon as user selects the second app (even if animations aren't complete) - * Sets [secondUser] from that of the pendingIntent + * To be called as soon as user selects the second app (even if animations aren't complete) Sets + * [secondUser] from that of the pendingIntent + * * @param pendingIntent The second PendingIntent that will be launched. */ fun setSecondTask(pendingIntent: PendingIntent, itemInfo: ItemInfo) { @@ -173,9 +189,9 @@ class SplitSelectDataHolder( } /** - * Similar to [setSecondTask] except this is to be called for widgets which can pass through - * an extra intent from their RemoteResponse. - * See [android.widget.RemoteViews.RemoteResponse.getLaunchOptions].first + * Similar to [setSecondTask] except this is to be called for widgets which can pass through an + * extra intent from their RemoteResponse. See + * [android.widget.RemoteViews.RemoteResponse.getLaunchOptions].first */ fun setSecondWidget(pendingIntent: PendingIntent, widgetIntent: Intent?, itemInfo: ItemInfo) { setSecondTask(pendingIntent, itemInfo) @@ -184,8 +200,7 @@ class SplitSelectDataHolder( private fun getShortcutInfo(intent: Intent?, user: UserHandle?): ShortcutInfo? { val intentPackage = intent?.getPackage() ?: return null - val shortcutId = intent.getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID) - ?: return null + val shortcutId = intent.getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID) ?: return null try { val context: Context = if (user != null) { @@ -200,9 +215,7 @@ class SplitSelectDataHolder( return null } - /** - * Converts intents to pendingIntents, associating the [user] with the intent if provided - */ + /** Converts intents to pendingIntents, associating the [user] with the intent if provided */ private fun getPendingIntent(intent: Intent?, user: UserHandle?): PendingIntent? { if (intent != initialIntent && intent != secondIntent) { throw IllegalStateException("Invalid intent to convert to PendingIntent") @@ -211,12 +224,21 @@ class SplitSelectDataHolder( return if (intent == null) { null } else if (user != null) { - PendingIntent.getActivityAsUser(context, 0, intent, - PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT, - null /* options */, user) + PendingIntent.getActivityAsUser( + context, + 0, + intent, + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT, + null /* options */, + user + ) } else { - PendingIntent.getActivity(context, 0, intent, - PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT) + PendingIntent.getActivity( + context, + 0, + intent, + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT + ) } } @@ -224,7 +246,7 @@ class SplitSelectDataHolder( * @return [SplitLaunchData] with the necessary fields populated as determined by * [SplitLaunchData.splitLaunchType]. This is to be used for launching splitscreen */ - fun getSplitLaunchData() : SplitLaunchData { + fun getSplitLaunchData(): SplitLaunchData { // Convert all intents to shortcut infos to see if determine if we launch shortcut or intent convertIntentsToFinalTypes() val splitLaunchType = getSplitLaunchType() @@ -241,7 +263,7 @@ class SplitSelectDataHolder( * [SplitLaunchData.splitLaunchType]. This is to be used for launching an initially selected * split task in fullscreen */ - fun getFullscreenLaunchData() : SplitLaunchData { + fun getFullscreenLaunchData(): SplitLaunchData { // Convert all intents to shortcut infos to determine if we launch shortcut or intent convertIntentsToFinalTypes() val splitLaunchType = getFullscreenLaunchType() @@ -249,21 +271,22 @@ class SplitSelectDataHolder( return generateSplitLaunchData(splitLaunchType) } - private fun generateSplitLaunchData(@SplitLaunchType splitLaunchType: Int) : SplitLaunchData { + private fun generateSplitLaunchData(@SplitLaunchType splitLaunchType: Int): SplitLaunchData { return SplitLaunchData( - splitLaunchType, - initialTaskId, - secondTaskId, - initialPendingIntent, - secondPendingIntent, - widgetSecondIntent, - initialUser?.identifier ?: -1, - secondUser?.identifier ?: -1, - initialShortcut, - secondShortcut, - itemInfo, - splitEvent, - initialStagePosition) + splitLaunchType, + initialTaskId, + secondTaskId, + initialPendingIntent, + secondPendingIntent, + widgetSecondIntent, + initialUser?.identifier ?: -1, + secondUser?.identifier ?: -1, + initialShortcut, + secondShortcut, + itemInfo, + splitEvent, + initialStagePosition + ) } /** @@ -273,8 +296,7 @@ class SplitSelectDataHolder( * Note that both [initialIntent] and [secondIntent] will be nullified on method return * * One caveat is that if [secondPendingIntent] is set, we will use that and *not* attempt to - * convert [secondIntent]. - * This also leaves [widgetSecondIntent] untouched. + * convert [secondIntent]. This also leaves [widgetSecondIntent] untouched. */ private fun convertIntentsToFinalTypes() { initialShortcut = getShortcutInfo(initialIntent, initialUser) @@ -297,8 +319,8 @@ class SplitSelectDataHolder( } /** - * Only valid data fields at this point should be tasks, shortcuts, or pendingIntents - * Intents need to be converted in [convertIntentsToFinalTypes] prior to calling this method + * Only valid data fields at this point should be tasks, shortcuts, or pendingIntents Intents + * need to be converted in [convertIntentsToFinalTypes] prior to calling this method */ @VisibleForTesting @SplitLaunchType @@ -354,41 +376,40 @@ class SplitSelectDataHolder( } data class SplitLaunchData( - @SplitLaunchType - val splitLaunchType: Int, - var initialTaskId: Int = INVALID_TASK_ID, - var secondTaskId: Int = INVALID_TASK_ID, - var initialPendingIntent: PendingIntent? = null, - var secondPendingIntent: PendingIntent? = null, - var widgetSecondIntent: Intent? = null, - var initialUserId: Int = -1, - var secondUserId: Int = -1, - var initialShortcut: ShortcutInfo? = null, - var secondShortcut: ShortcutInfo? = null, - var itemInfo: ItemInfo? = null, - var splitEvent: EventEnum? = null, - val initialStagePosition: Int = STAGE_POSITION_UNDEFINED + @SplitLaunchType val splitLaunchType: Int, + var initialTaskId: Int = INVALID_TASK_ID, + var secondTaskId: Int = INVALID_TASK_ID, + var initialPendingIntent: PendingIntent? = null, + var secondPendingIntent: PendingIntent? = null, + var widgetSecondIntent: Intent? = null, + var initialUserId: Int = -1, + var secondUserId: Int = -1, + var initialShortcut: ShortcutInfo? = null, + var secondShortcut: ShortcutInfo? = null, + var itemInfo: ItemInfo? = null, + var splitEvent: EventEnum? = null, + val initialStagePosition: Int = STAGE_POSITION_UNDEFINED ) /** - * @return `true` if first task has been selected and waiting for the second task to be - * chosen + * @return `true` if first task has been selected and waiting for the second task to be chosen */ fun isSplitSelectActive(): Boolean { return isInitialTaskIntentSet() && !isSecondTaskIntentSet() } /** - * @return `true` if the first and second task have been chosen and split is waiting to - * be launched + * @return `true` if the first and second task have been chosen and split is waiting to be + * launched */ fun isBothSplitAppsConfirmed(): Boolean { return isInitialTaskIntentSet() && isSecondTaskIntentSet() } private fun isInitialTaskIntentSet(): Boolean { - return initialTaskId != INVALID_TASK_ID || initialIntent != null || - initialPendingIntent != null + return initialTaskId != INVALID_TASK_ID || + initialIntent != null || + initialPendingIntent != null } fun getInitialTaskId(): Int { @@ -416,8 +437,9 @@ class SplitSelectDataHolder( } private fun isSecondTaskIntentSet(): Boolean { - return secondTaskId != INVALID_TASK_ID || secondIntent != null - || secondPendingIntent != null + return secondTaskId != INVALID_TASK_ID || + secondIntent != null || + secondPendingIntent != null } fun resetState() { @@ -437,7 +459,7 @@ class SplitSelectDataHolder( } fun dump(prefix: String, writer: PrintWriter) { - writer.println("$prefix ${javaClass.simpleName}") + writer.println("$prefix SplitSelectDataHolder") writer.println("$prefix\tinitialStagePosition= $initialStagePosition") writer.println("$prefix\tinitialTaskId= $initialTaskId") writer.println("$prefix\tsecondTaskId= $secondTaskId") diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java index f823affef9..99f10a75db 100644 --- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java +++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java @@ -46,7 +46,7 @@ import com.android.wm.shell.pip.PipContentOverlay; * when swiping up (in gesture navigation mode). */ public class SwipePipToHomeAnimator extends RectFSpringAnim { - private static final String TAG = SwipePipToHomeAnimator.class.getSimpleName(); + private static final String TAG = "SwipePipToHomeAnimator"; private static final float END_PROGRESS = 1.0f; diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java index 21c9e09b10..69137cc1b6 100644 --- a/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java +++ b/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java @@ -37,7 +37,7 @@ import java.util.function.Predicate; * @param Type of object stored in the cache */ public class TaskKeyByLastActiveTimeCache implements TaskKeyCache { - private static final String TAG = TaskKeyByLastActiveTimeCache.class.getSimpleName(); + private static final String TAG = "TaskKeyByLastActiveTimeCache"; private final AtomicInteger mMaxSize; private final Map> mMap; // To sort task id by last active time diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java index 57bc2d78ba..1bedad4ef2 100644 --- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java +++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java @@ -66,7 +66,7 @@ import java.util.function.Consumer; // TODO(b/249371338): TaskView needs to be refactored to have better support for N tasks. public class DesktopTaskView extends TaskView { - private static final String TAG = DesktopTaskView.class.getSimpleName(); + private static final String TAG = "DesktopTaskView"; private static final boolean DEBUG = false; diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java index f92b9ddb55..4df9414832 100644 --- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java +++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java @@ -79,7 +79,7 @@ public final class DigitalWellBeingToast { static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS); static final int MINUTE_MS = 60000; - private static final String TAG = DigitalWellBeingToast.class.getSimpleName(); + private static final String TAG = "DigitalWellBeingToast"; private final RecentsViewContainer mContainer; private final TaskView mTaskView; diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java index 1b4d22fc2e..1ccb7640a8 100644 --- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java @@ -63,7 +63,7 @@ import java.util.function.Consumer; */ public class GroupedTaskView extends TaskView { - private static final String TAG = GroupedTaskView.class.getSimpleName(); + private static final String TAG = "GroupedTaskView"; // TODO(b/336612373): Support new TTV for GroupedTaskView private TaskThumbnailViewDeprecated mSnapshotView2; private TaskViewIcon mIconView2; diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java index 8a917d68bf..ba60141aab 100644 --- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java +++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java @@ -38,6 +38,7 @@ import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.NavigationMode; import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks; +import com.android.quickstep.util.AppPairsController; import com.android.quickstep.util.LayoutUtils; import java.lang.annotation.Retention; @@ -139,6 +140,7 @@ public class OverviewActionsView extends FrameLayo protected DeviceProfile mDp; private final Rect mTaskSize = new Rect(); private boolean mIsGroupedTask = false; + private boolean mCanSaveAppPair = false; public OverviewActionsView(Context context) { this(context, null); @@ -245,9 +247,12 @@ public class OverviewActionsView extends FrameLayo * Updates a batch of flags to hide and show actions buttons when a grouped task (split screen) * is focused. * @param isGroupedTask True if the focused task is a grouped task. + * @param canSaveAppPair True if the focused task is a grouped task and can be saved as an app + * pair. */ - public void updateForGroupedTask(boolean isGroupedTask) { + public void updateForGroupedTask(boolean isGroupedTask, boolean canSaveAppPair) { mIsGroupedTask = isGroupedTask; + mCanSaveAppPair = canSaveAppPair; updateActionButtonsVisibility(); } @@ -264,7 +269,7 @@ public class OverviewActionsView extends FrameLayo private void updateActionButtonsVisibility() { assert mDp != null; boolean showSingleTaskActions = !mIsGroupedTask; - boolean showGroupActions = mIsGroupedTask && mDp.isTablet; + boolean showGroupActions = mIsGroupedTask && mDp.isTablet && mCanSaveAppPair; getActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showSingleTaskActions ? 1 : 0); getGroupActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showGroupActions ? 1 : 0); } diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 4431d62c5f..a242e1d61d 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -4021,7 +4021,9 @@ public abstract class RecentsView com.android.settings
+ + + + diff --git a/src/com/android/launcher3/DevicePaddings.java b/src/com/android/launcher3/DevicePaddings.java index 08fb47b2cf..8494d11937 100644 --- a/src/com/android/launcher3/DevicePaddings.java +++ b/src/com/android/launcher3/DevicePaddings.java @@ -47,7 +47,7 @@ public class DevicePaddings { private static final String WORKSPACE_BOTTOM_PADDING = "workspaceBottomPadding"; private static final String HOTSEAT_BOTTOM_PADDING = "hotseatBottomPadding"; - private static final String TAG = DevicePaddings.class.getSimpleName(); + private static final String TAG = "DevicePaddings"; private static final boolean DEBUG = false; ArrayList mDevicePaddings = new ArrayList<>(); diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 869b9956c1..a4ae1c8050 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -52,6 +52,7 @@ import com.android.launcher3.pm.InstallSessionTracker; import com.android.launcher3.pm.UserCache; import com.android.launcher3.util.LockedUserState; import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.SafeCloseable; @@ -181,7 +182,7 @@ public class LauncherAppState implements SafeCloseable { mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName, mIconProvider); mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext), - iconCacheFileName != null); + PackageManagerHelper.INSTANCE.get(context), iconCacheFileName != null); mOnTerminateCallback.add(mIconCache::close); mOnTerminateCallback.add(mModel::destroy); } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index be98589467..e3da38937d 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -98,6 +98,8 @@ public class LauncherModel implements InstallSessionTracker.Callback { @NonNull private final LauncherAppState mApp; @NonNull + private final PackageManagerHelper mPmHelper; + @NonNull private final ModelDbController mModelDbController; @NonNull private final Object mLock = new Object(); @@ -152,12 +154,13 @@ public class LauncherModel implements InstallSessionTracker.Callback { LauncherModel(@NonNull final Context context, @NonNull final LauncherAppState app, @NonNull final IconCache iconCache, @NonNull final AppFilter appFilter, - final boolean isPrimaryInstance) { + @NonNull final PackageManagerHelper pmHelper, final boolean isPrimaryInstance) { mApp = app; + mPmHelper = pmHelper; mModelDbController = new ModelDbController(context); mBgAllAppsList = new AllAppsList(iconCache, appFilter); - mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel, - isPrimaryInstance); + mModelDelegate = ModelDelegate.newInstance(context, app, mPmHelper, mBgAllAppsList, + mBgDataModel, isPrimaryInstance); } @NonNull diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index cc9f08eadc..365fbd3919 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -788,7 +788,7 @@ public abstract class PagedView extends ViewGrou if (mScroller.isFinished() && pageScrollChanged) { // TODO(b/246283207): Remove logging once root cause of flake detected. if (Utilities.isRunningInTestHarness() && !(this instanceof Workspace)) { - Log.d("b/246283207", this.getClass().getSimpleName() + "#onLayout() -> " + Log.d("b/246283207", TAG + "#onLayout() -> " + "if(mScroller.isFinished() && pageScrollChanged) -> getNextPage(): " + getNextPage() + ", getScrollForPage(getNextPage()): " + getScrollForPage(getNextPage())); diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 42297089e1..4a26a18a27 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -134,6 +134,10 @@ public final class Utilities { @ChecksSdkIntAtLeast(api = VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "U") public static final boolean ATLEAST_U = Build.VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE; + @ChecksSdkIntAtLeast(api = VERSION_CODES.VANILLA_ICE_CREAM, codename = "V") + public static final boolean ATLEAST_V = Build.VERSION.SDK_INT + >= VERSION_CODES.VANILLA_ICE_CREAM; + /** * Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}. */ diff --git a/src/com/android/launcher3/apppairs/AppPairIcon.java b/src/com/android/launcher3/apppairs/AppPairIcon.java index 1f73241ed0..32445ecddb 100644 --- a/src/com/android/launcher3/apppairs/AppPairIcon.java +++ b/src/com/android/launcher3/apppairs/AppPairIcon.java @@ -30,6 +30,8 @@ import androidx.annotation.Nullable; import com.android.launcher3.BubbleTextView; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Flags; +import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.Reorderable; @@ -85,6 +87,11 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab : activity.getLayoutInflater(); AppPairIcon icon = (AppPairIcon) inflater.inflate(resId, group, false); + if (Flags.enableFocusOutline() && activity instanceof Launcher) { + icon.setOnFocusChangeListener(((Launcher) activity).getFocusHandler()); + icon.setDefaultFocusHighlightEnabled(false); + } + // Sort contents, so that left-hand app comes first appPairInfo.getContents().sort(Comparator.comparingInt(a -> a.rank)); diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java index 39c1243bd1..64ebbf3833 100644 --- a/src/com/android/launcher3/model/AllAppsList.java +++ b/src/com/android/launcher3/model/AllAppsList.java @@ -60,7 +60,7 @@ public class AllAppsList { private static final String TAG = "AllAppsList"; private static final Consumer NO_OP_CONSUMER = a -> { }; - + private static final boolean DEBUG = true; public static final int DEFAULT_APPLICATIONS_NUMBER = 42; @@ -220,6 +220,11 @@ public class AllAppsList { updatedAppInfos.add(appInfo); } else if (installInfo.state == PackageInstallInfo.STATUS_FAILED && !appInfo.isAppStartable()) { + if (DEBUG) { + Log.w(TAG, "updatePromiseInstallInfo: removing app due to install" + + " failure and appInfo not startable." + + " package=" + appInfo.getTargetPackage()); + } removeApp(i); } } @@ -301,6 +306,7 @@ public class AllAppsList { Context context, String packageName, UserHandle user) { final ApiWrapper apiWrapper = ApiWrapper.INSTANCE.get(context); final UserCache userCache = UserCache.getInstance(context); + final PackageManagerHelper pmHelper = PackageManagerHelper.INSTANCE.get(context); final List matches = context.getSystemService(LauncherApps.class) .getActivityList(packageName, user); if (matches.size() > 0) { @@ -311,7 +317,10 @@ public class AllAppsList { if (user.equals(applicationInfo.user) && packageName.equals(applicationInfo.componentName.getPackageName())) { if (!findActivity(matches, applicationInfo.componentName)) { - Log.w(TAG, "Changing shortcut target due to app component name change."); + if (DEBUG) { + Log.w(TAG, "Changing shortcut target due to app component name change." + + " package=" + packageName); + } removeApp(i); } } @@ -330,12 +339,16 @@ public class AllAppsList { applicationInfo.sectionName = mIndex.computeSectionName(applicationInfo.title); applicationInfo.intent = launchIntent; AppInfo.updateRuntimeFlagsForActivityTarget(applicationInfo, info, - userCache.getUserInfo(user), apiWrapper); + userCache.getUserInfo(user), apiWrapper, pmHelper); mDataChanged = true; } } } else { // Remove all data for this package. + if (DEBUG) { + Log.w(TAG, "updatePromiseInstallInfo: no Activities matched updated package," + + " removing all apps from package=" + packageName); + } for (int i = data.size() - 1; i >= 0; i--) { final AppInfo applicationInfo = data.get(i); if (user.equals(applicationInfo.user) diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index 0875974a21..84130c796f 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -57,6 +57,7 @@ import com.android.launcher3.util.ContentWriter; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSparseArrayMap; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.UserIconInfo; import java.net.URISyntaxException; @@ -73,6 +74,7 @@ public class LoaderCursor extends CursorWrapper { private final LauncherAppState mApp; private final Context mContext; + private final PackageManagerHelper mPmHelper; private final IconCache mIconCache; private final InvariantDeviceProfile mIDP; private final @Nullable LauncherRestoreEventLogger mRestoreEventLogger; @@ -114,6 +116,7 @@ public class LoaderCursor extends CursorWrapper { public int restoreFlag; public LoaderCursor(Cursor cursor, LauncherAppState app, UserManagerState userManagerState, + PackageManagerHelper pmHelper, @Nullable LauncherRestoreEventLogger restoreEventLogger) { super(cursor); @@ -121,6 +124,7 @@ public class LoaderCursor extends CursorWrapper { allUsers = userManagerState.allUsers; mContext = app.getContext(); mIconCache = app.getIconCache(); + mPmHelper = pmHelper; mIDP = app.getInvariantDeviceProfile(); mRestoreEventLogger = restoreEventLogger; @@ -368,7 +372,7 @@ public class LoaderCursor extends CursorWrapper { if (mActivityInfo != null) { AppInfo.updateRuntimeFlagsForActivityTarget(info, mActivityInfo, userIconInfo, - ApiWrapper.INSTANCE.get(mContext)); + ApiWrapper.INSTANCE.get(mContext), mPmHelper); } // from the db diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 876bed421f..0d40a2432e 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -435,7 +435,8 @@ public class LoaderTask implements Runnable { mShortcutKeyToPinnedShortcuts = new HashMap<>(); final LoaderCursor c = new LoaderCursor( dbController.query(TABLE_NAME, null, selection, null, null), - mApp, mUserManagerState, mIsRestoreFromBackup ? restoreEventLogger : null); + mApp, mUserManagerState, mPmHelper, + mIsRestoreFromBackup ? restoreEventLogger : null); final Bundle extras = c.getExtras(); mDbName = extras == null ? null : extras.getString(ModelDbController.EXTRA_DB_NAME); try { @@ -697,7 +698,7 @@ public class LoaderTask implements Runnable { for (int i = 0; i < apps.size(); i++) { LauncherActivityInfo app = apps.get(i); AppInfo appInfo = new AppInfo(app, mUserCache.getUserInfo(user), - ApiWrapper.INSTANCE.get(mApp.getContext()), quietMode); + ApiWrapper.INSTANCE.get(mApp.getContext()), mPmHelper, quietMode); if (Flags.enableSupportForArchiving() && app.getApplicationInfo().isArchived) { // For archived apps, include progress info in case there is a pending // install session post restart of device. diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java index 8360b14fa4..2264d35df1 100644 --- a/src/com/android/launcher3/model/ModelDelegate.java +++ b/src/com/android/launcher3/model/ModelDelegate.java @@ -26,6 +26,7 @@ import androidx.annotation.WorkerThread; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.shortcuts.ShortcutKey; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.ResourceBasedOverride; import java.io.FileDescriptor; @@ -41,15 +42,16 @@ public class ModelDelegate implements ResourceBasedOverride { * Creates and initializes a new instance of the delegate */ public static ModelDelegate newInstance( - Context context, LauncherAppState app, AllAppsList appsList, BgDataModel dataModel, - boolean isPrimaryInstance) { + Context context, LauncherAppState app, PackageManagerHelper pmHelper, + AllAppsList appsList, BgDataModel dataModel, boolean isPrimaryInstance) { ModelDelegate delegate = Overrides.getObject( ModelDelegate.class, context, R.string.model_delegate_class); - delegate.init(app, appsList, dataModel, isPrimaryInstance); + delegate.init(app, pmHelper, appsList, dataModel, isPrimaryInstance); return delegate; } protected final Context mContext; + protected PackageManagerHelper mPmHelper; protected LauncherAppState mApp; protected AllAppsList mAppsList; protected BgDataModel mDataModel; @@ -62,9 +64,10 @@ public class ModelDelegate implements ResourceBasedOverride { /** * Initializes the object with the given params. */ - private void init(LauncherAppState app, AllAppsList appsList, + private void init(LauncherAppState app, PackageManagerHelper pmHelper, AllAppsList appsList, BgDataModel dataModel, boolean isPrimaryInstance) { this.mApp = app; + this.mPmHelper = pmHelper; this.mAppsList = appsList; this.mDataModel = dataModel; this.mIsPrimaryInstance = isPrimaryInstance; diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index 802faae136..29d22699a6 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -74,8 +74,8 @@ import java.util.stream.Collectors; public class PackageUpdatedTask implements ModelUpdateTask { // TODO(b/290090023): Set to false after root causing is done. - private static final boolean DEBUG = true; private static final String TAG = "PackageUpdatedTask"; + private static final boolean DEBUG = true; public static final int OP_NONE = 0; public static final int OP_ADD = 1; @@ -117,29 +117,39 @@ public class PackageUpdatedTask implements ModelUpdateTask { : ItemInfoMatcher.ofPackages(packageSet, mUser); final HashSet removedComponents = new HashSet<>(); final HashMap> activitiesLists = new HashMap<>(); - + if (DEBUG) { + Log.d(TAG, "Package updated: mOp=" + getOpString() + + " packages=" + Arrays.toString(packages)); + } switch (mOp) { case OP_ADD: { for (int i = 0; i < N; i++) { - if (DEBUG) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]); iconCache.updateIconsForPkg(packages[i], mUser); if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) { + if (DEBUG) { + Log.d(TAG, "OP_ADD: PROMISE_APPS_IN_ALL_APPS enabled:" + + " removing promise icon apps from package=" + packages[i]); + } appsList.removePackage(packages[i], mUser); } - activitiesLists.put( - packages[i], appsList.addPackage(context, packages[i], mUser)); + activitiesLists.put(packages[i], + appsList.addPackage(context, packages[i], mUser)); } flagOp = FlagOp.NO_OP.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE); break; } case OP_UPDATE: - try (SafeCloseable t = - appsList.trackRemoves(a -> removedComponents.add(a.componentName))) { + try (SafeCloseable t = appsList.trackRemoves(a -> { + Log.d(TAG, "OP_UPDATE - AllAppsList.trackRemoves callback:" + + " removed component=" + a.componentName + + " id=" + a.id + + " Look for earlier AllAppsList logs to find more information."); + removedComponents.add(a.componentName); + })) { for (int i = 0; i < N; i++) { - if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]); iconCache.updateIconsForPkg(packages[i], mUser); - activitiesLists.put( - packages[i], appsList.updatePackage(context, packages[i], mUser)); + activitiesLists.put(packages[i], + appsList.updatePackage(context, packages[i], mUser)); } } // Since package was just updated, the target must be available now. @@ -147,14 +157,15 @@ public class PackageUpdatedTask implements ModelUpdateTask { break; case OP_REMOVE: { for (int i = 0; i < N; i++) { - FileLog.d(TAG, "Removing app icon: " + packages[i]); iconCache.removeIconsForPkg(packages[i], mUser); } // Fall through } case OP_UNAVAILABLE: for (int i = 0; i < N; i++) { - if (DEBUG) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]); + if (DEBUG) { + Log.d(TAG, getOpString() + ": removing package=" + packages[i]); + } appsList.removePackage(packages[i], mUser); } flagOp = FlagOp.NO_OP.addFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE); @@ -163,7 +174,6 @@ public class PackageUpdatedTask implements ModelUpdateTask { case OP_UNSUSPEND: flagOp = FlagOp.NO_OP.setFlag( WorkspaceItemInfo.FLAG_DISABLED_SUSPENDED, mOp == OP_SUSPEND); - if (DEBUG) Log.d(TAG, "mAllAppsList.(un)suspend " + N); appsList.updateDisabledFlags(matcher, flagOp); break; case OP_USER_AVAILABILITY_CHANGE: { @@ -249,12 +259,21 @@ public class PackageUpdatedTask implements ModelUpdateTask { infoUpdated = true; } else if (si.hasPromiseIconUi()) { removedShortcuts.add(si.id); + if (DEBUG) { + Log.d(TAG, "Removing restored shortcut promise icon" + + " that no longer points to valid component." + + " id=" + si.id + + ", package=" + si.getTargetPackage()); + } return; } } else if (!isTargetValid) { removedShortcuts.add(si.id); - FileLog.e(TAG, "Restored shortcut no longer valid " - + si.getIntent()); + FileLog.e(TAG, "Removing shortcut that no longer points to" + + " valid component." + + " id=" + si.id + + " package=" + si.getTargetPackage() + + " status=" + si.status); return; } else { si.status = WorkspaceItemInfo.DEFAULT; @@ -269,6 +288,8 @@ public class PackageUpdatedTask implements ModelUpdateTask { if (isNewApkAvailable) { List activities = activitiesLists.get( packageName); + // TODO: See if we can migrate this to + // AppInfo#updateRuntimeFlagsForActivityTarget si.setProgressLevel( activities == null || activities.isEmpty() ? 100 @@ -334,7 +355,8 @@ public class PackageUpdatedTask implements ModelUpdateTask { if (!removedShortcuts.isEmpty()) { taskController.deleteAndBindComponentsRemoved( ItemInfoMatcher.ofItemIds(removedShortcuts), - "removed because the target component is invalid"); + "removing shortcuts with invalid target components." + + " ids=" + removedShortcuts); } if (!widgets.isEmpty()) { @@ -346,6 +368,9 @@ public class PackageUpdatedTask implements ModelUpdateTask { if (mOp == OP_REMOVE) { // Mark all packages in the broadcast to be removed Collections.addAll(removedPackages, packages); + if (DEBUG) { + Log.d(TAG, "OP_REMOVE: removing packages=" + Arrays.toString(packages)); + } // No need to update the removedComponents as // removedPackages is a super-set of removedComponents @@ -354,6 +379,10 @@ public class PackageUpdatedTask implements ModelUpdateTask { final LauncherApps launcherApps = context.getSystemService(LauncherApps.class); for (int i=0; i "NONE"; + case OP_ADD -> "ADD"; + case OP_UPDATE -> "UPDATE"; + case OP_REMOVE -> "REMOVE"; + case OP_UNAVAILABLE -> "UNAVAILABLE"; + case OP_SUSPEND -> "SUSPEND"; + case OP_UNSUSPEND -> "UNSUSPEND"; + case OP_USER_AVAILABILITY_CHANGE -> "USER_AVAILABILITY_CHANGE"; + default -> "UNKNOWN"; + }; + } } diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt index cea4380e5e..90e47d66cc 100644 --- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt +++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt @@ -336,7 +336,8 @@ class WorkspaceItemProcessor( info, activityInfo, userCache.getUserInfo(c.user), - ApiWrapper.INSTANCE[app.context] + ApiWrapper.INSTANCE[app.context], + pmHelper ) } if ( diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java index 18aa6e75f7..a4281f8677 100644 --- a/src/com/android/launcher3/model/data/AppInfo.java +++ b/src/com/android/launcher3/model/data/AppInfo.java @@ -90,12 +90,12 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory { */ public AppInfo(Context context, LauncherActivityInfo info, UserHandle user) { this(info, UserCache.INSTANCE.get(context).getUserInfo(user), - ApiWrapper.INSTANCE.get(context), + ApiWrapper.INSTANCE.get(context), PackageManagerHelper.INSTANCE.get(context), context.getSystemService(UserManager.class).isQuietModeEnabled(user)); } public AppInfo(LauncherActivityInfo info, UserIconInfo userIconInfo, - ApiWrapper apiWrapper, boolean quietModeEnabled) { + ApiWrapper apiWrapper, PackageManagerHelper pmHelper, boolean quietModeEnabled) { this.componentName = info.getComponentName(); this.container = CONTAINER_ALL_APPS; this.user = userIconInfo.user; @@ -105,7 +105,7 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory { runtimeStatusFlags |= FLAG_DISABLED_QUIET_USER; } uid = info.getApplicationInfo().uid; - updateRuntimeFlagsForActivityTarget(this, info, userIconInfo, apiWrapper); + updateRuntimeFlagsForActivityTarget(this, info, userIconInfo, apiWrapper, pmHelper); } public AppInfo(AppInfo info) { @@ -184,7 +184,7 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory { */ public static boolean updateRuntimeFlagsForActivityTarget( ItemInfoWithIcon info, LauncherActivityInfo lai, UserIconInfo userIconInfo, - ApiWrapper apiWrapper) { + ApiWrapper apiWrapper, PackageManagerHelper pmHelper) { final int oldProgressLevel = info.getProgressLevel(); final int oldRuntimeStatusFlags = info.runtimeStatusFlags; ApplicationInfo appInfo = lai.getApplicationInfo(); @@ -216,6 +216,8 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory { PackageManagerHelper.getLoadingProgress(lai), PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING); info.setNonResizeable(apiWrapper.isNonResizeableActivity(lai)); + info.setSupportsMultiInstance( + pmHelper.supportsMultiInstance(lai.getComponentName())); return (oldProgressLevel != info.getProgressLevel()) || (oldRuntimeStatusFlags != info.runtimeStatusFlags); } diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java index 72e85c7cb7..b82d0a0985 100644 --- a/src/com/android/launcher3/model/data/ItemInfo.java +++ b/src/com/android/launcher3/model/data/ItemInfo.java @@ -73,6 +73,7 @@ import java.util.Optional; * Represents an item in the launcher. */ public class ItemInfo { + private static final String TAG = "ItemInfo"; public static final boolean DEBUG = false; public static final int NO_ID = -1; @@ -285,7 +286,7 @@ public class ItemInfo { @Override @NonNull public final String toString() { - return getClass().getSimpleName() + "(" + dumpProperties() + ")"; + return TAG + "(" + dumpProperties() + ")"; } @NonNull diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java index d4c25cba8c..6ac44ffc93 100644 --- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java +++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java @@ -126,6 +126,11 @@ public abstract class ItemInfoWithIcon extends ItemInfo { */ public static final int FLAG_NOT_RESIZEABLE = 1 << 15; + /** + * Flag indicating whether the package related to the item & user supports multiple instances. + */ + public static final int FLAG_SUPPORTS_MULTI_INSTANCE = 1 << 16; + /** * Status associated with the system state of the underlying item. This is calculated every * time a new info is created and not persisted on the disk. @@ -251,6 +256,24 @@ public abstract class ItemInfoWithIcon extends ItemInfo { } } + /** + * Sets whether this app info supports multi-instance. + */ + protected void setSupportsMultiInstance(boolean supportsMultiInstance) { + if (supportsMultiInstance) { + runtimeStatusFlags |= FLAG_SUPPORTS_MULTI_INSTANCE; + } else { + runtimeStatusFlags &= ~FLAG_SUPPORTS_MULTI_INSTANCE; + } + } + + /** + * Returns whether this app info supports multi-instance. + */ + public boolean supportsMultiInstance() { + return (runtimeStatusFlags & FLAG_SUPPORTS_MULTI_INSTANCE) != 0; + } + /** * Sets whether this app info is non-resizeable. */ @@ -301,4 +324,11 @@ public abstract class ItemInfoWithIcon extends ItemInfo { drawable.setIsDisabled(isDisabled()); return drawable; } + + @Override + protected String dumpProperties() { + return super.dumpProperties() + + " supportsMultiInstance=" + supportsMultiInstance() + + " nonResizeable=" + isNonResizeable(); + } } diff --git a/src/com/android/launcher3/pm/PackageInstallInfo.java b/src/com/android/launcher3/pm/PackageInstallInfo.java index 1797c1fc81..23d3b61f82 100644 --- a/src/com/android/launcher3/pm/PackageInstallInfo.java +++ b/src/com/android/launcher3/pm/PackageInstallInfo.java @@ -22,6 +22,7 @@ import android.os.UserHandle; import androidx.annotation.NonNull; public final class PackageInstallInfo { + private static final String TAG = "PackageInstallInfo"; public static final int STATUS_INSTALLED = 0; public static final int STATUS_INSTALLING = 1; @@ -61,7 +62,7 @@ public final class PackageInstallInfo { @Override public String toString() { - return getClass().getSimpleName() + "(" + dumpProperties() + ")"; + return TAG + "(" + dumpProperties() + ")"; } private String dumpProperties() { diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java index b992a92ee4..3ae643e5b5 100644 --- a/src/com/android/launcher3/provider/LauncherDbUtils.java +++ b/src/com/android/launcher3/provider/LauncherDbUtils.java @@ -44,6 +44,7 @@ import com.android.launcher3.pm.UserCache; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; +import com.android.launcher3.util.PackageManagerHelper; /** * A set of utility methods for Launcher DB used for DB updates and migration. @@ -107,9 +108,11 @@ public class LauncherDbUtils { Cursor c = db.query( Favorites.TABLE_NAME, null, "itemType = 1", null, null, null, null); UserManagerState ums = new UserManagerState(); + PackageManagerHelper pmHelper = PackageManagerHelper.INSTANCE.get(context); ums.init(UserCache.INSTANCE.get(context), context.getSystemService(UserManager.class)); - LoaderCursor lc = new LoaderCursor(c, LauncherAppState.getInstance(context), ums, null); + LoaderCursor lc = new LoaderCursor(c, LauncherAppState.getInstance(context), ums, pmHelper, + null); IntSet deletedShortcuts = new IntSet(); while (lc.moveToNext()) { diff --git a/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt b/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt index 7502a43ff9..2c3035f3ac 100644 --- a/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt +++ b/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt @@ -135,7 +135,7 @@ data class HotseatSpec( hotseatQsbSpace.fixedSize + edgePadding.fixedSize <= maxAvailableSize private fun logError(message: String) { - Log.e(LOG_TAG, "${this::class.simpleName}#isValid - $message - $this") + Log.e(LOG_TAG, "$LOG_TAG #isValid - $message - $this") } companion object { diff --git a/src/com/android/launcher3/responsive/ResponsiveCellSpecsProvider.kt b/src/com/android/launcher3/responsive/ResponsiveCellSpecsProvider.kt index a4b25e529a..ef9b7dfc6f 100644 --- a/src/com/android/launcher3/responsive/ResponsiveCellSpecsProvider.kt +++ b/src/com/android/launcher3/responsive/ResponsiveCellSpecsProvider.kt @@ -31,7 +31,7 @@ class ResponsiveCellSpecsProvider(groupOfSpecs: List check(group.widthSpecs.isEmpty() && group.heightSpecs.isNotEmpty()) { - "${this::class.simpleName} is invalid, only heightSpecs are allowed - " + + "$LOG_TAG is invalid, only heightSpecs are allowed - " + "width list size = ${group.widthSpecs.size}; " + "height list size = ${group.heightSpecs.size}." } @@ -65,6 +65,7 @@ class ResponsiveCellSpecsProvider(groupOfSpecs: List check(group.widthSpecs.isNotEmpty() && group.heightSpecs.isNotEmpty()) { - "${this::class.simpleName} is incomplete - " + + "$LOG_TAG is incomplete - " + "width list size = ${group.widthSpecs.size}; " + "height list size = ${group.heightSpecs.size}." } @@ -124,6 +124,7 @@ class ResponsiveSpecsProvider( } companion object { + private const val LOG_TAG = "ResponsiveSpecsProvider" @JvmStatic fun create( resourceHelper: ResourceHelper, diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java index 0ed6ea05c4..f46dcd328c 100644 --- a/src/com/android/launcher3/touch/ItemClickHandler.java +++ b/src/com/android/launcher3/touch/ItemClickHandler.java @@ -84,7 +84,8 @@ import java.util.function.Consumer; */ public class ItemClickHandler { - private static final String TAG = ItemClickHandler.class.getSimpleName(); + private static final String TAG = "ItemClickHandler"; + private static final boolean DEBUG = true; /** * Instance used for click handling on items @@ -110,7 +111,19 @@ public class ItemClickHandler { startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher); } else if (tag instanceof LauncherAppWidgetInfo) { if (v instanceof PendingAppWidgetHostView) { + if (DEBUG) { + String targetPackage = ((LauncherAppWidgetInfo) tag).getTargetPackage(); + Log.d(TAG, "onClick: PendingAppWidgetHostView clicked for" + + " package=" + targetPackage); + } onClickPendingWidget((PendingAppWidgetHostView) v, launcher); + } else { + if (DEBUG) { + String targetPackage = ((LauncherAppWidgetInfo) tag).getTargetPackage(); + Log.d(TAG, "onClick: LauncherAppWidgetInfo clicked," + + " but not instance of PendingAppWidgetHostView. Returning." + + " package=" + targetPackage); + } } } else if (tag instanceof ItemClickProxy) { ((ItemClickProxy) tag).onItemClicked(v); @@ -120,6 +133,10 @@ public class ItemClickHandler { launcher.getString(R.string.long_accessible_way_to_add_shortcut)); Snackbar.show(launcher, msg, null); } else if (tag instanceof PendingAddWidgetInfo) { + if (DEBUG) { + String targetPackage = ((PendingAddWidgetInfo) tag).getTargetPackage(); + Log.d(TAG, "onClick: PendingAddWidgetInfo clicked for package=" + targetPackage); + } CharSequence msg = Utilities.wrapForTts( launcher.getText(R.string.long_press_widget_to_add), launcher.getString(R.string.long_accessible_way_to_add)); @@ -199,6 +216,9 @@ public class ItemClickHandler { LauncherAppWidgetProviderInfo appWidgetInfo = new WidgetManagerHelper(launcher) .findProvider(info.providerName, info.user); if (appWidgetInfo == null) { + Log.e(TAG, "onClickPendingWidget: Pending widget ready for click setup," + + " but LauncherAppWidgetProviderInfo was null. Returning." + + " component=" + info.getTargetComponent()); return; } WidgetAddFlowHandler addFlowHandler = new WidgetAddFlowHandler(appWidgetInfo); @@ -206,6 +226,10 @@ public class ItemClickHandler { if (info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) { if (!info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) { // This should not happen, as we make sure that an Id is allocated during bind. + Log.e(TAG, "onClickPendingWidget: Pending widget ready for click setup," + + " and LauncherAppWidgetProviderInfo was found. However," + + " no appWidgetId was allocated. Returning." + + " component=" + info.getTargetComponent()); return; } addFlowHandler.startBindFlow(launcher, info.appWidgetId, info, diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java index 3684f569c3..f7c4df4527 100644 --- a/src/com/android/launcher3/util/PackageManagerHelper.java +++ b/src/com/android/launcher3/util/PackageManagerHelper.java @@ -17,6 +17,7 @@ package com.android.launcher3.util; import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE; +import static android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI; import android.content.ActivityNotFoundException; import android.content.ComponentName; @@ -73,10 +74,14 @@ public class PackageManagerHelper implements SafeCloseable{ @NonNull private final LauncherApps mLauncherApps; + private final String[] mLegacyMultiInstanceSupportedApps; + public PackageManagerHelper(@NonNull final Context context) { mContext = context; mPm = context.getPackageManager(); mLauncherApps = Objects.requireNonNull(context.getSystemService(LauncherApps.class)); + mLegacyMultiInstanceSupportedApps = mContext.getResources().getStringArray( + R.array.config_appsSupportMultiInstancesSplit); } @Override @@ -159,11 +164,23 @@ public class PackageManagerHelper implements SafeCloseable{ } } + /** + * Returns the preferred launch activity intent for a given package. + */ @Nullable public Intent getAppLaunchIntent(@Nullable final String pkg, @NonNull final UserHandle user) { + LauncherActivityInfo info = getAppLaunchInfo(pkg, user); + return info != null ? AppInfo.makeLaunchIntent(info) : null; + } + + /** + * Returns the preferred launch activity for a given package. + */ + @Nullable + public LauncherActivityInfo getAppLaunchInfo(@Nullable final String pkg, + @NonNull final UserHandle user) { List activities = mLauncherApps.getActivityList(pkg, user); - return activities.isEmpty() ? null : - AppInfo.makeLaunchIntent(activities.get(0)); + return activities.isEmpty() ? null : activities.get(0); } /** @@ -285,4 +302,47 @@ public class PackageManagerHelper implements SafeCloseable{ return (info.flags & ApplicationInfo.FLAG_INSTALLED) != 0 || ( Flags.enableSupportForArchiving() && info.isArchived); } + + /** + * Returns whether the given component or its application has the multi-instance property set. + */ + public boolean supportsMultiInstance(@NonNull ComponentName component) { + // Check the legacy hardcoded allowlist first + for (String pkg : mLegacyMultiInstanceSupportedApps) { + if (pkg.equals(component.getPackageName())) { + return true; + } + } + + // Check app multi-instance properties after V + if (!Utilities.ATLEAST_V) { + return false; + } + + try { + // Check if the component has the multi-instance property + return mPm.getProperty(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, component) + .getBoolean(); + } catch (PackageManager.NameNotFoundException e1) { + try { + // Check if the application has the multi-instance property + return mPm.getProperty(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, + component.getPackageName()) + .getBoolean(); + } catch (PackageManager.NameNotFoundException e2) { + // Fall through + } + } + return false; + } + + /** + * Returns whether two apps should be considered the same for multi-instance purposes, which + * requires additional checks to ensure they can be started as multiple instances. + */ + public static boolean isSameAppForMultiInstance(@NonNull ItemInfo app1, + @NonNull ItemInfo app2) { + return app1.getTargetPackage().equals(app2.getTargetPackage()) + && app1.user.equals(app2.user); + } } diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java index 2f0da03418..bb4f04008b 100644 --- a/src/com/android/launcher3/views/ArrowTipView.java +++ b/src/com/android/launcher3/views/ArrowTipView.java @@ -54,7 +54,7 @@ import com.android.launcher3.graphics.TriangleShape; */ public class ArrowTipView extends AbstractFloatingView { - private static final String TAG = ArrowTipView.class.getSimpleName(); + private static final String TAG = "ArrowTipView"; private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000; private static final long SHOW_DELAY_MS = 200; private static final long SHOW_DURATION_MS = 300; diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index 0d07f639a1..1d5a9dcb97 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -68,7 +68,7 @@ import java.util.function.Supplier; public class FloatingIconView extends FrameLayout implements Animator.AnimatorListener, OnGlobalLayoutListener, FloatingView { - private static final String TAG = FloatingIconView.class.getSimpleName(); + private static final String TAG = "FloatingIconView"; // Manages loading the icon on a worker thread private static @Nullable IconLoadResult sIconLoadResult; diff --git a/src/com/android/launcher3/widget/WidgetManagerHelper.java b/src/com/android/launcher3/widget/WidgetManagerHelper.java index d293d15f38..9132b4f18c 100644 --- a/src/com/android/launcher3/widget/WidgetManagerHelper.java +++ b/src/com/android/launcher3/widget/WidgetManagerHelper.java @@ -26,6 +26,7 @@ import android.content.Context; import android.os.Build; import android.os.Bundle; import android.os.UserHandle; +import android.util.Log; import android.widget.RemoteViews; import androidx.annotation.NonNull; @@ -104,6 +105,8 @@ public class WidgetManagerHelper { // If exception is thrown because of device is locked, it means a race condition occurs // that the user got locked again while launcher is processing the event. In this case // we should return empty list. + Log.e(TAG, "getAllProviders: Error getting installed providers for" + + " package=" + packageUser.mPackageName, e); return Collections.emptyList(); } } @@ -133,6 +136,8 @@ public class WidgetManagerHelper { return LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info); } } + Log.w(TAG, "findProvider: No App Widget Provider found for component=" + provider + + " user=" + user); return null; } diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java index 9c4db60f69..f835e1860e 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java @@ -36,6 +36,7 @@ import android.widget.LinearLayout; import android.widget.ScrollView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.Px; import com.android.launcher3.DeviceProfile; @@ -78,6 +79,7 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { private boolean mOldIsSwipeToDismissInProgress; private int mActivePage = -1; + @Nullable private PackageUserKey mSelectedHeader; public WidgetsTwoPaneSheet(Context context, AttributeSet attrs, int defStyleAttr) { @@ -230,7 +232,8 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { if (mSuggestedWidgetsContainer == null && mRecommendedWidgetsCount > 0) { setupSuggestedWidgets(LayoutInflater.from(getContext())); mSuggestedWidgetsHeader.callOnClick(); - } else if (mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey)) { + } else if (mSelectedHeader != null + && mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey)) { // Reselect widget if we are reloading recommendations while it is currently showing. selectWidgetCell(mWidgetRecommendationsContainer, getLastSelectedWidgetItem()); } @@ -280,8 +283,8 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { mRightPaneScrollView.setScrollY(0); mRightPane.setAccessibilityPaneTitle(suggestionsRightPaneTitle); mSuggestedWidgetsPackageUserKey = PackageUserKey.fromPackageItemInfo(packageItemInfo); - final boolean isChangingHeaders = - !mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey); + final boolean isChangingHeaders = mSelectedHeader == null + || !mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey); if (isChangingHeaders) { // If switching from another header, unselect any WidgetCells. This is necessary // because we do not clear/recycle the WidgetCells in the recommendations container diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml index a9b75eaf41..8c5195e096 100644 --- a/tests/AndroidManifest-common.xml +++ b/tests/AndroidManifest-common.xml @@ -412,5 +412,9 @@ android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" tools:node="remove" /> + + diff --git a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java index b6b2261ccb..4cd2a0755a 100644 --- a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java +++ b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java @@ -202,6 +202,7 @@ public class PrivateProfileManagerTest { } @Test + @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/339109319 public void openPrivateSpaceSettings_triggersCorrectIntent() { Intent expectedIntent = ApiWrapper.INSTANCE.get(mContext).getPrivateSpaceSettingsIntent(); ArgumentCaptor acIntent = ArgumentCaptor.forClass(Intent.class); diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java index 56ac960c21..b4945d7129 100644 --- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java +++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java @@ -79,6 +79,7 @@ public class LoaderCursorTest { private LauncherModelHelper mModelHelper; private LauncherAppState mApp; + private PackageManagerHelper mPmHelper; private MatrixCursor mCursor; private InvariantDeviceProfile mIDP; @@ -92,6 +93,7 @@ public class LoaderCursorTest { mContext = mModelHelper.sandboxContext; mIDP = InvariantDeviceProfile.INSTANCE.get(mContext); mApp = LauncherAppState.getInstance(mContext); + mPmHelper = PackageManagerHelper.INSTANCE.get(mContext); mCursor = new MatrixCursor(new String[] { ICON, TITLE, _ID, CONTAINER, ITEM_TYPE, @@ -101,7 +103,7 @@ public class LoaderCursorTest { }); UserManagerState ums = new UserManagerState(); - mLoaderCursor = new LoaderCursor(mCursor, mApp, ums, null); + mLoaderCursor = new LoaderCursor(mCursor, mApp, ums, mPmHelper, null); ums.allUsers.put(0, Process.myUserHandle()); } diff --git a/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java b/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java index d1da5f4f2d..b5e797ed39 100644 --- a/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java +++ b/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java @@ -34,6 +34,7 @@ import android.platform.test.flag.junit.DeviceFlagsValueProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Before; import org.junit.Rule; @@ -63,6 +64,8 @@ public final class PackageManagerHelperTest { mContext = mock(Context.class); mLauncherApps = mock(LauncherApps.class); when(mContext.getSystemService(eq(LauncherApps.class))).thenReturn(mLauncherApps); + when(mContext.getResources()).thenReturn( + InstrumentationRegistry.getInstrumentation().getTargetContext().getResources()); mPackageManagerHelper = new PackageManagerHelper(mContext); } diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java index 8d3a631744..4be46ab621 100644 --- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java +++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java @@ -17,6 +17,7 @@ package com.android.launcher3.tapl; import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL; +import android.text.TextUtils; import android.widget.TextView; import androidx.test.uiautomator.By; @@ -117,4 +118,28 @@ public class SearchResultFromQsb implements SearchInputSource { public SearchResultFromQsb getSearchResultForInput() { return this; } + + /** Verify a tile is present by checking its title and subtitle. */ + public void verifyTileIsPresent(String title, String subtitle) { + ArrayList searchResults = + new ArrayList<>(mLauncher.waitForObjectsInContainer( + mLauncher.waitForSystemLauncherObject(SEARCH_CONTAINER_RES_ID), + By.clazz(TextView.class))); + boolean foundTitle = false; + boolean foundSubtitle = false; + for (UiObject2 uiObject: searchResults) { + String currentString = uiObject.getText(); + if (TextUtils.equals(currentString, title)) { + foundTitle = true; + } else if (TextUtils.equals(currentString, subtitle)) { + foundSubtitle = true; + } + } + if (!foundTitle) { + mLauncher.fail("Tile not found for title: " + title); + } + if (!foundSubtitle) { + mLauncher.fail("Tile not found for subtitle: " + subtitle); + } + } }