Add multi-instance state to item infos

- Add legacy resource for supported multi-instance apps that
  matches the current SystemUI resource of the same name, and will
  be removed as apps migrate to the V manifest property to declare
  multi-instance support
- Load the multi-instance state from PackageManager when the db is
  first loaded or when packages are updated
- The multi-instance check is then used to determine if an app pair
  can be saved (ie. whether the action can be shown)

Bug: 323112914
Test: atest NexusLauncherTests

Change-Id: I565b4bee4ab5f7040910306b1fd60a4fc3bf9a1c
This commit is contained in:
Winson Chung
2024-05-08 21:14:57 +00:00
parent bfdd7d27c0
commit 94e8ad0731
22 changed files with 277 additions and 122 deletions
@@ -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<ShortcutKey, ShortcutInfo> 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<ShortcutKey, ShortcutInfo> pinnedShortcuts, int maxCount, int container) {
PackageManagerHelper pmHelper, Map<ShortcutKey, ShortcutInfo> 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);
@@ -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;
}
@@ -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<TaskView.TaskContainer> 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.
* <br>
@@ -119,31 +173,23 @@ public class AppPairsController {
List<TaskView.TaskContainer> 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);
}
/**
@@ -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<T extends OverlayUICallbacks> 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<T extends OverlayUICallbacks> 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<T extends OverlayUICallbacks> 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);
}
@@ -4011,7 +4011,9 @@ public abstract class RecentsView<CONTAINER_TYPE extends Context & RecentsViewCo
* * Device is large screen
*/
private void updateCurrentTaskActionsVisibility() {
boolean isCurrentSplit = getCurrentPageTaskView() instanceof GroupedTaskView;
TaskView taskView = getCurrentPageTaskView();
boolean isCurrentSplit = taskView instanceof GroupedTaskView;
GroupedTaskView groupedTaskView = isCurrentSplit ? (GroupedTaskView) taskView : null;
// Update flags to see if entire actions bar should be hidden.
if (!FeatureFlags.enableAppPairs()) {
mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SCREEN, isCurrentSplit);
@@ -4019,9 +4021,11 @@ public abstract class RecentsView<CONTAINER_TYPE extends Context & RecentsViewCo
mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SELECT_ACTIVE, isSplitSelectionActive());
// Update flags to see if actions bar should show buttons for a single task or a pair of
// tasks.
mActionsView.updateForGroupedTask(isCurrentSplit);
boolean canSaveAppPair = isCurrentSplit && supportsAppPairs() &&
getSplitSelectController().getAppPairsController().canSaveAppPair(groupedTaskView);
mActionsView.updateForGroupedTask(isCurrentSplit, canSaveAppPair);
boolean isCurrentDesktop = getCurrentPageTaskView() instanceof DesktopTaskView;
boolean isCurrentDesktop = taskView instanceof DesktopTaskView;
mActionsView.updateHiddenFlags(HIDDEN_DESKTOP, isCurrentDesktop);
}
+7
View File
@@ -223,4 +223,11 @@
<string-array name="skip_private_profile_shortcut_packages" translatable="false">
<item>com.android.settings</item>
</string-array>
<!-- Legacy list of components supporting multiple instances.
DO NOT ADD TO THIS LIST. Apps should use the PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI
property to declare multi-instance support in V+. This resource should match the resource
of the same name in SystemUI. -->
<string-array name="config_appsSupportMultiInstancesSplit">
</string-array>
</resources>
@@ -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);
}
+6 -3
View File
@@ -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
+4
View File
@@ -132,6 +132,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)}.
*/
@@ -301,6 +301,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<LauncherActivityInfo> matches = context.getSystemService(LauncherApps.class)
.getActivityList(packageName, user);
if (matches.size() > 0) {
@@ -330,7 +331,7 @@ 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;
}
}
@@ -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
@@ -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.
@@ -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;
@@ -126,8 +126,8 @@ public class PackageUpdatedTask implements ModelUpdateTask {
if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
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;
@@ -138,8 +138,8 @@ public class PackageUpdatedTask implements ModelUpdateTask {
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.
@@ -269,6 +269,8 @@ public class PackageUpdatedTask implements ModelUpdateTask {
if (isNewApkAvailable) {
List<LauncherActivityInfo> activities = activitiesLists.get(
packageName);
// TODO: See if we can migrate this to
// AppInfo#updateRuntimeFlagsForActivityTarget
si.setProgressLevel(
activities == null || activities.isEmpty()
? 100
@@ -399,7 +401,8 @@ public class PackageUpdatedTask implements ModelUpdateTask {
return false;
}
// Try to find the best match activity.
Intent intent = new PackageManagerHelper(context).getAppLaunchIntent(packageName, mUser);
Intent intent = PackageManagerHelper.INSTANCE.get(context)
.getAppLaunchIntent(packageName, mUser);
if (intent != null) {
si.intent = intent;
si.status = WorkspaceItemInfo.DEFAULT;
@@ -336,7 +336,8 @@ class WorkspaceItemProcessor(
info,
activityInfo,
userCache.getUserInfo(c.user),
ApiWrapper.INSTANCE[app.context]
ApiWrapper.INSTANCE[app.context],
pmHelper
)
}
if (
@@ -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);
}
@@ -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();
}
}
@@ -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()) {
@@ -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<LauncherActivityInfo> 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);
}
}
+4
View File
@@ -412,5 +412,9 @@
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
<property
android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
android:value="true"/>
</application>
</manifest>
@@ -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());
}
@@ -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);
}