From a474a9bcf5d533ac942c58409e45e5ec6d8b4893 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 4 May 2017 16:47:11 -0700 Subject: [PATCH] Simplifying logic for managed for icon addition > Checking for duplicate icons before adding new icons For O and above, icon addition is controlled using SessionCommitReceiver. As long as the Launcher is the default app, it will keep adding icons on the homescreen. Apps installed while launcher was not the default homescreen, no icons will be added. For below O, icons are added based on package event. As long as the Launcher process is running, it will keep adding icons on the homescreen. Apps installed while the launcher app was dead, no icons will be added. Bug: 37528649 Bug: 37082950 Bug: 34112546 Change-Id: Ic99501fa476c00474a479f2a36c24614bfa3f4bf --- .../launcher3/InstallShortcutReceiver.java | 57 ++-- src/com/android/launcher3/Launcher.java | 16 +- src/com/android/launcher3/LauncherModel.java | 61 +--- .../launcher3/SessionCommitReceiver.java | 17 +- src/com/android/launcher3/Workspace.java | 5 +- .../launcher3/compat/UserManagerCompatVL.java | 4 +- .../model/AddWorkspaceItemsTask.java | 47 ++- .../launcher3/model/PackageUpdatedTask.java | 14 +- .../launcher3/util/CachedPackageTracker.java | 188 ----------- .../util/ManagedProfileHeuristic.java | 291 ++++++++---------- .../model/AddWorkspaceItemsTaskTest.java | 7 +- 11 files changed, 248 insertions(+), 459 deletions(-) delete mode 100644 src/com/android/launcher3/util/CachedPackageTracker.java diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java index ce8557065e..b136e7d816 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -34,6 +34,7 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.Base64; import android.util.Log; +import android.util.Pair; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; @@ -59,6 +60,16 @@ import java.util.List; import java.util.Set; public class InstallShortcutReceiver extends BroadcastReceiver { + + public static final int FLAG_ACTIVITY_PAUSED = 1; + public static final int FLAG_LOADER_RUNNING = 2; + public static final int FLAG_DRAG_AND_DROP = 4; + public static final int FLAG_BULK_ADD = 4; + + // Determines whether to defer installing shortcuts immediately until + // processAllPendingInstalls() is called. + private static int sInstallQueueDisabledFlags = 0; + private static final String TAG = "InstallShortcutReceiver"; private static final boolean DBG = false; @@ -151,10 +162,6 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } } - // Determines whether to defer installing shortcuts immediately until - // processAllPendingInstalls() is called. - private static boolean mUseInstallQueue = false; - public void onReceive(Context context, Intent data) { if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) { return; @@ -207,7 +214,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { public static ShortcutInfo fromShortcutIntent(Context context, Intent data) { PendingInstallShortcutInfo info = createPendingInfo(context, data); - return info == null ? null : (ShortcutInfo) info.getItemInfo(); + return info == null ? null : (ShortcutInfo) info.getItemInfo().first; } public static void queueShortcut(ShortcutInfoCompat info, Context context) { @@ -245,27 +252,28 @@ public class InstallShortcutReceiver extends BroadcastReceiver { private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) { // Queue the item up for adding if launcher has not loaded properly yet - LauncherAppState app = LauncherAppState.getInstance(context); - boolean launcherNotLoaded = app.getModel().getCallback() == null; - addToInstallQueue(Utilities.getPrefs(context), info); - if (!mUseInstallQueue && !launcherNotLoaded) { - flushInstallQueue(context); - } + flushInstallQueue(context); } - static void enableInstallQueue() { - mUseInstallQueue = true; + public static void enableInstallQueue(int flag) { + sInstallQueueDisabledFlags |= flag; } - static void disableAndFlushInstallQueue(Context context) { - mUseInstallQueue = false; + public static void disableAndFlushInstallQueue(int flag, Context context) { + sInstallQueueDisabledFlags &= ~flag; flushInstallQueue(context); } static void flushInstallQueue(Context context) { + LauncherModel model = LauncherAppState.getInstance(context).getModel(); + boolean launcherNotLoaded = model.getCallback() == null; + if (sInstallQueueDisabledFlags != 0 || launcherNotLoaded) { + return; + } + ArrayList items = getAndClearInstallQueue(context); if (!items.isEmpty()) { - LauncherAppState.getInstance(context).getModel().addAndBindAddedWorkspaceItems( + model.addAndBindAddedWorkspaceItems( new LazyShortcutsProvider(context.getApplicationContext(), items)); } } @@ -439,7 +447,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } } - public ItemInfo getItemInfo() { + public Pair getItemInfo() { if (activityInfo != null) { AppInfo appInfo = new AppInfo(mContext, activityInfo, user); final LauncherAppState app = LauncherAppState.getInstance(mContext); @@ -459,11 +467,11 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } }); } - return si; + return Pair.create((ItemInfo) si, (Object) activityInfo); } else if (shortcutInfo != null) { ShortcutInfo si = new ShortcutInfo(shortcutInfo, mContext); si.iconBitmap = LauncherIcons.createShortcutIcon(shortcutInfo, mContext); - return si; + return Pair.create((ItemInfo) si, (Object) shortcutInfo); } else if (providerInfo != null) { LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo .fromProviderInfo(mContext, providerInfo); @@ -475,9 +483,10 @@ public class InstallShortcutReceiver extends BroadcastReceiver { widgetInfo.minSpanY = info.minSpanY; widgetInfo.spanX = Math.min(info.spanX, idp.numColumns); widgetInfo.spanY = Math.min(info.spanY, idp.numRows); - return widgetInfo; + return Pair.create((ItemInfo) widgetInfo, (Object) providerInfo); } else { - return createShortcutInfo(data, LauncherAppState.getInstance(mContext)); + ShortcutInfo si = createShortcutInfo(data, LauncherAppState.getInstance(mContext)); + return Pair.create((ItemInfo) si, null); } } @@ -588,7 +597,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { return new PendingInstallShortcutInfo(info, original.mContext); } - private static class LazyShortcutsProvider extends Provider> { + private static class LazyShortcutsProvider extends Provider>> { private final Context mContext; private final ArrayList mPendingItems; @@ -603,9 +612,9 @@ public class InstallShortcutReceiver extends BroadcastReceiver { * packageManager and icon cache. */ @Override - public ArrayList get() { + public ArrayList> get() { Preconditions.assertNonUiThread(); - ArrayList installQueue = new ArrayList<>(); + ArrayList> installQueue = new ArrayList<>(); LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext); for (PendingInstallShortcutInfo pendingInfo : mPendingItems) { // If the intent specifies a package, make sure the package exists diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index b63bbd5480..b9b561020c 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1036,13 +1036,12 @@ public class Launcher extends BaseActivity updateInteraction(Workspace.State.NORMAL, mWorkspace.getState()); mWorkspace.onResume(); - if (!isWorkspaceLoading()) { - // Process any items that were added while Launcher was away. - InstallShortcutReceiver.disableAndFlushInstallQueue(this); + // Process any items that were added while Launcher was away. + InstallShortcutReceiver.disableAndFlushInstallQueue( + InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this); - // Refresh shortcuts if the permission changed. - mModel.refreshShortcutsIfRequired(); - } + // Refresh shortcuts if the permission changed. + mModel.refreshShortcutsIfRequired(); if (shouldShowDiscoveryBounce()) { mAllAppsController.showDiscoveryBounce(); @@ -1057,7 +1056,7 @@ public class Launcher extends BaseActivity @Override protected void onPause() { // Ensure that items added to Launcher are queued until Launcher returns - InstallShortcutReceiver.enableInstallQueue(); + InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED); super.onPause(); mPaused = true; @@ -3655,7 +3654,8 @@ public class Launcher extends BaseActivity mPendingActivityResult = null; } - InstallShortcutReceiver.disableAndFlushInstallQueue(this); + InstallShortcutReceiver.disableAndFlushInstallQueue( + InstallShortcutReceiver.FLAG_LOADER_RUNNING, this); NotificationListener.setNotificationsChangedListener(mPopupDataProvider); diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 12789c55a5..b5ca301d0c 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -40,6 +40,7 @@ import android.text.TextUtils; import android.util.Log; import android.util.LongSparseArray; import android.util.MutableInt; +import android.util.Pair; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherAppsCompat; @@ -138,12 +139,6 @@ public class LauncherModel extends BroadcastReceiver } } - /** - * Set of runnables to be called on the background thread after the workspace binding - * is complete. - */ - static final ArrayList mBindCompleteRunnables = new ArrayList(); - @Thunk WeakReference mCallbacks; // < only access in worker thread > @@ -248,18 +243,11 @@ public class LauncherModel extends BroadcastReceiver CacheDataUpdatedTask.OP_SESSION_UPDATE, Process.myUserHandle(), packages)); } - /** - * Adds the provided items to the workspace. - */ - public void addAndBindAddedWorkspaceItems(List workspaceApps) { - addAndBindAddedWorkspaceItems(Provider.of(workspaceApps)); - } - /** * Adds the provided items to the workspace. */ public void addAndBindAddedWorkspaceItems( - Provider> appsProvider) { + Provider>> appsProvider) { enqueueModelUpdateTask(new AddWorkspaceItemsTask(appsProvider)); } @@ -529,7 +517,7 @@ public class LauncherModel extends BroadcastReceiver */ public boolean startLoader(int synchronousBindPage) { // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems - InstallShortcutReceiver.enableInstallQueue(); + InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING); synchronized (mLock) { // Don't bother to start the thread if we know it's not going to do anything if (mCallbacks != null && mCallbacks.get() != null) { @@ -607,7 +595,6 @@ public class LauncherModel extends BroadcastReceiver private Context mContext; private int mPageToBindFirst; - @Thunk boolean mIsLoadingAndBindingWorkspace; private boolean mStopped; LoaderTask(Context context, int pageToBindFirst) { @@ -675,8 +662,6 @@ public class LauncherModel extends BroadcastReceiver try { long now = 0; if (DEBUG_LOADERS) Log.d(TAG, "step 1.1: loading workspace"); - // Set to false in bindWorkspace() - mIsLoadingAndBindingWorkspace = true; loadWorkspace(); verifyNotStopped(); @@ -1584,18 +1569,6 @@ public class LauncherModel extends BroadcastReceiver callbacks.finishBindingItems(); } - mIsLoadingAndBindingWorkspace = false; - - // Run all the bind complete runnables after workspace is bound. - if (!mBindCompleteRunnables.isEmpty()) { - synchronized (mBindCompleteRunnables) { - for (final Runnable r : mBindCompleteRunnables) { - runOnWorkerThread(r); - } - mBindCompleteRunnables.clear(); - } - } - // If we're profiling, ensure this is the last thing in the queue. if (DEBUG_LOADERS) { Log.d(TAG, "bound workspace in " @@ -1710,31 +1683,7 @@ public class LauncherModel extends BroadcastReceiver mBgAllAppsList.add(new AppInfo(app, user, quietMode), app); } - final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user); - if (heuristic != null) { - final Runnable r = new Runnable() { - - @Override - public void run() { - heuristic.processUserApps(apps); - } - }; - mUiExecutor.execute(new Runnable() { - - @Override - public void run() { - // Check isLoadingWorkspace on the UI thread, as it is updated on - // the UI thread. - if (mIsLoadingAndBindingWorkspace) { - synchronized (mBindCompleteRunnables) { - mBindCompleteRunnables.add(r); - } - } else { - runOnWorkerThread(r); - } - } - }); - } + ManagedProfileHeuristic.onAllAppsLoaded(mContext, apps, user); } if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) { @@ -1768,8 +1717,6 @@ public class LauncherModel extends BroadcastReceiver } } }); - // Cleanup any data stored for a deleted user. - ManagedProfileHeuristic.processAllUsers(profiles, mContext); if (DEBUG_LOADERS) { Log.d(TAG, "Icons processed in " + (SystemClock.uptimeMillis() - loadTime) + "ms"); diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java index 61bcc178cf..8caba75cd5 100644 --- a/src/com/android/launcher3/SessionCommitReceiver.java +++ b/src/com/android/launcher3/SessionCommitReceiver.java @@ -67,18 +67,19 @@ public class SessionCommitReceiver extends BroadcastReceiver { SessionInfo info = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION); UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER); - if (TextUtils.isEmpty(info.getAppPackageName()) || - info.getInstallReason() != PackageManager.INSTALL_REASON_USER) { - return; + if (Process.myUserHandle().equals(user)) { + if (TextUtils.isEmpty(info.getAppPackageName()) || + info.getInstallReason() != PackageManager.INSTALL_REASON_USER) { + return; + } } - if (!Process.myUserHandle().equals(user)) { - // Managed profile is handled using ManagedProfileHeuristic - return; - } + queueAppIconAddition(context, info.getAppPackageName(), user); + } + public static void queueAppIconAddition(Context context, String packageName, UserHandle user) { List activities = LauncherAppsCompat.getInstance(context) - .getActivityList(info.getAppPackageName(), user); + .getActivityList(packageName, user); if (activities == null || activities.isEmpty()) { // no activity found return; diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index b3dd7ac60e..672203cc50 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -451,7 +451,7 @@ public class Workspace extends PagedView mLauncher.lockScreenOrientation(); mLauncher.onInteractionBegin(); // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging - InstallShortcutReceiver.enableInstallQueue(); + InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_DRAG_AND_DROP); // Do not add a new page if it is a accessible drag which was not started by the workspace. // We do not support accessibility drag from other sources and instead provide a direct @@ -504,7 +504,8 @@ public class Workspace extends PagedView mLauncher.unlockScreenOrientation(false); // Re-enable any Un/InstallShortcutReceiver and now process any queued items - InstallShortcutReceiver.disableAndFlushInstallQueue(getContext()); + InstallShortcutReceiver.disableAndFlushInstallQueue( + InstallShortcutReceiver.FLAG_DRAG_AND_DROP, getContext()); mOutlineProvider = null; mDragInfo = null; diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java index 45525f5214..c7f88f63d1 100644 --- a/src/com/android/launcher3/compat/UserManagerCompatVL.java +++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java @@ -22,8 +22,8 @@ import android.content.pm.PackageManager; import android.os.UserHandle; import android.os.UserManager; -import com.android.launcher3.Utilities; import com.android.launcher3.util.LongArrayMap; +import com.android.launcher3.util.ManagedProfileHeuristic; import java.util.ArrayList; import java.util.Collections; @@ -122,7 +122,7 @@ public class UserManagerCompatVL extends UserManagerCompat { @Override public long getUserCreationTime(UserHandle user) { - SharedPreferences prefs = Utilities.getPrefs(mContext); + SharedPreferences prefs = ManagedProfileHeuristic.prefs(mContext); String key = USER_CREATION_TIME_KEY + getSerialNumberForUser(user); if (!prefs.contains(key)) { prefs.edit().putLong(key, System.currentTimeMillis()).apply(); diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java index 10fb5828cb..2e8e15bf7b 100644 --- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java +++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java @@ -17,6 +17,8 @@ package com.android.launcher3.model; import android.content.Context; import android.content.Intent; +import android.content.pm.LauncherActivityInfo; +import android.os.Process; import android.os.UserHandle; import android.util.LongSparseArray; import android.util.Pair; @@ -35,9 +37,11 @@ import com.android.launcher3.LauncherSettings; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; import com.android.launcher3.util.GridOccupancy; +import com.android.launcher3.util.ManagedProfileHeuristic.UserFolderInfo; import com.android.launcher3.util.Provider; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; /** @@ -45,18 +49,18 @@ import java.util.List; */ public class AddWorkspaceItemsTask extends ExtendedModelTask { - private final Provider> mAppsProvider; + private final Provider>> mAppsProvider; /** * @param appsProvider items to add on the workspace */ - public AddWorkspaceItemsTask(Provider> appsProvider) { + public AddWorkspaceItemsTask(Provider>> appsProvider) { mAppsProvider = appsProvider; } @Override public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { - List workspaceApps = mAppsProvider.get(); + List> workspaceApps = mAppsProvider.get(); if (workspaceApps.isEmpty()) { return; } @@ -64,13 +68,17 @@ public class AddWorkspaceItemsTask extends ExtendedModelTask { final ArrayList addedItemsFinal = new ArrayList<>(); final ArrayList addedWorkspaceScreensFinal = new ArrayList<>(); + HashMap userFolderMap = new HashMap<>(); // Get the list of workspace screens. We need to append to this list and // can not use sBgWorkspaceScreens because loadWorkspace() may not have been // called. ArrayList workspaceScreens = LauncherModel.loadWorkspaceScreensDb(context); synchronized(dataModel) { - for (ItemInfo item : workspaceApps) { + + List filteredItems = new ArrayList<>(); + for (Pair entry : workspaceApps) { + ItemInfo item = entry.first; if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { // Short-circuit this logic if the icon exists somewhere on the workspace @@ -79,6 +87,32 @@ public class AddWorkspaceItemsTask extends ExtendedModelTask { } } + if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { + if (item instanceof AppInfo) { + item = ((AppInfo) item).makeShortcut(); + } + + if (!Process.myUserHandle().equals(item.user)) { + // Check if this belongs to a work folder. + if (!(entry.second instanceof LauncherActivityInfo)) { + continue; + } + + UserFolderInfo userFolderInfo = userFolderMap.get(item.user); + if (userFolderInfo == null) { + userFolderInfo = new UserFolderInfo(context, item.user, dataModel); + userFolderMap.put(item.user, userFolderInfo); + } + item = userFolderInfo.convertToWorkspaceItem( + (ShortcutInfo) item, (LauncherActivityInfo) entry.second); + } + } + if (item != null) { + filteredItems.add(item); + } + } + + for (ItemInfo item : filteredItems) { // Find appropriate space for the item. Pair coords = findSpaceForItem(app, dataModel, workspaceScreens, addedWorkspaceScreensFinal, item.spanX, item.spanY); @@ -130,6 +164,10 @@ public class AddWorkspaceItemsTask extends ExtendedModelTask { } }); } + + for (UserFolderInfo userFolderInfo : userFolderMap.values()) { + userFolderInfo.applyPendingState(getModelWriter()); + } } protected void updateScreens(Context context, ArrayList workspaceScreens) { @@ -276,4 +314,5 @@ public class AddWorkspaceItemsTask extends ExtendedModelTask { } return occupied.findVacantCell(xy, spanX, spanY); } + } diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index b58efb6478..8380f01361 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -35,6 +35,7 @@ import com.android.launcher3.LauncherModel.CallbackTask; import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.SessionCommitReceiver; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; import com.android.launcher3.compat.LauncherAppsCompat; @@ -43,7 +44,6 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.ItemInfoMatcher; -import com.android.launcher3.util.ManagedProfileHeuristic; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; @@ -100,11 +100,11 @@ public class PackageUpdatedTask extends ExtendedModelTask { appsList.removePackage(packages[i], Process.myUserHandle()); } appsList.addPackage(context, packages[i], mUser); - } - ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser); - if (heuristic != null) { - heuristic.processPackageAdd(mPackages); + // Automatically add homescreen icon for work profile apps for below O device. + if (!Utilities.isAtLeastO() && !Process.myUserHandle().equals(mUser)) { + SessionCommitReceiver.queueAppIconAddition(context, packages[i], mUser); + } } break; } @@ -119,10 +119,6 @@ public class PackageUpdatedTask extends ExtendedModelTask { flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE); break; case OP_REMOVE: { - ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser); - if (heuristic != null) { - heuristic.processPackageRemoved(mPackages); - } for (int i = 0; i < N; i++) { iconCache.removeIconsForPkg(packages[i], mUser); } diff --git a/src/com/android/launcher3/util/CachedPackageTracker.java b/src/com/android/launcher3/util/CachedPackageTracker.java deleted file mode 100644 index 314b4c0edb..0000000000 --- a/src/com/android/launcher3/util/CachedPackageTracker.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.util; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.LauncherActivityInfo; -import android.os.UserHandle; - -import com.android.launcher3.Utilities; -import com.android.launcher3.compat.LauncherAppsCompat; -import com.android.launcher3.compat.LauncherAppsCompat.OnAppsChangedCallbackCompat; -import com.android.launcher3.compat.UserManagerCompat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Utility class to track list of installed packages. It persists the list so that apps - * installed/uninstalled while Launcher was dead can also be handled properly. - */ -public abstract class CachedPackageTracker implements OnAppsChangedCallbackCompat { - - protected static final String INSTALLED_PACKAGES_PREFIX = "installed_packages_for_user_"; - - protected final SharedPreferences mPrefs; - protected final UserManagerCompat mUserManager; - protected final LauncherAppsCompat mLauncherApps; - - public CachedPackageTracker(Context context, String preferenceFileName) { - mPrefs = context.getSharedPreferences(preferenceFileName, Context.MODE_PRIVATE); - mUserManager = UserManagerCompat.getInstance(context); - mLauncherApps = LauncherAppsCompat.getInstance(context); - } - - /** - * Checks the list of user apps, and generates package event accordingly. - * {@see #onLauncherAppsAdded}, {@see #onLauncherPackageRemoved} - */ - public void processUserApps(List apps, UserHandle user) { - String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user); - HashSet oldPackageSet = new HashSet<>(); - final boolean userAppsExisted = getUserApps(oldPackageSet, prefKey); - - HashSet packagesRemoved = new HashSet<>(oldPackageSet); - HashSet newPackageSet = new HashSet<>(); - ArrayList packagesAdded = new ArrayList<>(); - - for (LauncherActivityInfo info : apps) { - String packageName = info.getComponentName().getPackageName(); - newPackageSet.add(packageName); - packagesRemoved.remove(packageName); - - if (!oldPackageSet.contains(packageName)) { - oldPackageSet.add(packageName); - packagesAdded.add(new LauncherActivityInstallInfo( - info, info.getFirstInstallTime())); - } - } - - if (!packagesAdded.isEmpty() || !packagesRemoved.isEmpty()) { - mPrefs.edit().putStringSet(prefKey, newPackageSet).apply(); - - if (!packagesAdded.isEmpty()) { - Collections.sort(packagesAdded); - onLauncherAppsAdded(packagesAdded, user, userAppsExisted); - } - - if (!packagesRemoved.isEmpty()) { - for (String pkg : packagesRemoved) { - onLauncherPackageRemoved(pkg, user); - } - } - } - } - - /** - * Reads the list of user apps which have already been processed. - * @return false if the list didn't exist, true otherwise - */ - private boolean getUserApps(HashSet outExistingApps, String prefKey) { - Set userApps = mPrefs.getStringSet(prefKey, null); - if (userApps == null) { - return false; - } else { - outExistingApps.addAll(userApps); - return true; - } - } - - @Override - public void onPackageRemoved(String packageName, UserHandle user) { - String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user); - HashSet packageSet = new HashSet<>(); - if (getUserApps(packageSet, prefKey) && packageSet.remove(packageName)) { - mPrefs.edit().putStringSet(prefKey, packageSet).apply(); - } - - onLauncherPackageRemoved(packageName, user); - } - - @Override - public void onPackageAdded(String packageName, UserHandle user) { - String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user); - HashSet packageSet = new HashSet<>(); - final boolean userAppsExisted = getUserApps(packageSet, prefKey); - if (!packageSet.contains(packageName)) { - List activities = - mLauncherApps.getActivityList(packageName, user); - if (!activities.isEmpty()) { - LauncherActivityInfo activityInfo = activities.get(0); - - packageSet.add(packageName); - mPrefs.edit().putStringSet(prefKey, packageSet).apply(); - onLauncherAppsAdded(Arrays.asList( - new LauncherActivityInstallInfo(activityInfo, System.currentTimeMillis())), - user, userAppsExisted); - } - } - } - - @Override - public void onPackageChanged(String packageName, UserHandle user) { } - - @Override - public void onPackagesAvailable( - String[] packageNames, UserHandle user, boolean replacing) { } - - @Override - public void onPackagesUnavailable( - String[] packageNames, UserHandle user, boolean replacing) { } - - @Override - public void onPackagesSuspended(String[] packageNames, UserHandle user) { } - - @Override - public void onPackagesUnsuspended(String[] packageNames, UserHandle user) { } - - /** - * Called when new launcher apps are added. - * @param apps list of newly added activities. Only one entry per package is sent. - * @param user the user for this event. All activities in {@param apps} will belong to - * the same user. - * @param userAppsExisted false if the list was processed for the first time, like in case - * when Launcher was newly installed or a new user was added. - */ - protected abstract void onLauncherAppsAdded(List apps, - UserHandle user, boolean userAppsExisted); - - /** - * Called when apps are removed from the system. - */ - protected abstract void onLauncherPackageRemoved(String packageName, UserHandle user); - - public static class LauncherActivityInstallInfo - implements Comparable { - public final LauncherActivityInfo info; - public final long installTime; - - public LauncherActivityInstallInfo(LauncherActivityInfo info, long installTime) { - this.info = info; - this.installTime = installTime; - } - - @Override - public int compareTo(LauncherActivityInstallInfo another) { - return Utilities.longCompare(installTime, another.installTime); - } - } -} diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java index ce603c4c2d..091dd84bc1 100644 --- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java +++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java @@ -19,23 +19,23 @@ package com.android.launcher3.util; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.LauncherActivityInfo; +import android.os.Handler; import android.os.Process; import android.os.UserHandle; -import android.support.v4.os.BuildCompat; -import com.android.launcher3.AppInfo; import com.android.launcher3.FolderInfo; -import com.android.launcher3.IconCache; +import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.ItemInfo; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherFiles; import com.android.launcher3.LauncherModel; import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.R; import com.android.launcher3.SessionCommitReceiver; import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.Utilities; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.shortcuts.ShortcutInfoCompat; +import com.android.launcher3.model.BgDataModel; +import com.android.launcher3.model.ModelWriter; import java.util.ArrayList; import java.util.HashSet; @@ -47,11 +47,6 @@ import java.util.List; */ public class ManagedProfileHeuristic { - /** - * Maintain a set of packages installed per user. - */ - private static final String INSTALLED_PACKAGES_PREFIX = "installed_packages_for_user_"; - private static final String USER_FOLDER_ID_PREFIX = "user_folder_"; /** @@ -59,165 +54,154 @@ public class ManagedProfileHeuristic { */ private static final long AUTO_ADD_TO_FOLDER_DURATION = 8 * 60 * 60 * 1000; - public static ManagedProfileHeuristic get(Context context, UserHandle user) { - if (!Process.myUserHandle().equals(user)) { - return new ManagedProfileHeuristic(context, user); - } - return null; - } - - private final Context mContext; - private final LauncherModel mModel; - private final UserHandle mUser; - private final IconCache mIconCache; - private final boolean mAddIconsToHomescreen; - - private ManagedProfileHeuristic(Context context, UserHandle user) { - mContext = context; - mUser = user; - mModel = LauncherAppState.getInstance(context).getModel(); - mIconCache = LauncherAppState.getInstance(context).getIconCache(); - mAddIconsToHomescreen = - !BuildCompat.isAtLeastO() || SessionCommitReceiver.isEnabled(context); - } - - public void processPackageRemoved(String[] packages) { - Preconditions.assertWorkerThread(); - ManagedProfilePackageHandler handler = new ManagedProfilePackageHandler(); - for (String pkg : packages) { - handler.onPackageRemoved(pkg, mUser); - } - } - - public void processPackageAdd(String[] packages) { - Preconditions.assertWorkerThread(); - ManagedProfilePackageHandler handler = new ManagedProfilePackageHandler(); - for (String pkg : packages) { - handler.onPackageAdded(pkg, mUser); - } - } - - public void processUserApps(List apps) { - Preconditions.assertWorkerThread(); - new ManagedProfilePackageHandler().processUserApps(apps, mUser); - } - - private class ManagedProfilePackageHandler extends CachedPackageTracker { - - private ManagedProfilePackageHandler() { - super(mContext, LauncherFiles.MANAGED_USER_PREFERENCES_KEY); + public static void onAllAppsLoaded(final Context context, + List apps, UserHandle user) { + if (Process.myUserHandle().equals(user)) { + return; } - protected void onLauncherAppsAdded( - List apps, UserHandle user, boolean userAppsExisted) { - ArrayList workFolderApps = new ArrayList<>(); - ArrayList homescreenApps = new ArrayList<>(); + UserFolderInfo ufi = new UserFolderInfo(context, user, null); + // We only handle folder creation once. Later icon additions are handled using package + // or session events. + if (ufi.folderAlreadyCreated) { + return; + } - int count = apps.size(); - long folderCreationTime = - mUserManager.getUserCreationTime(user) + AUTO_ADD_TO_FOLDER_DURATION; + if (Utilities.isAtLeastO() && !SessionCommitReceiver.isEnabled(context)) { + // Just mark the folder id preference to avoid new folder creation later. + ufi.prefs.edit().putLong(ufi.folderIdKey, ItemInfo.NO_ID).apply(); + return; + } - boolean quietModeEnabled = UserManagerCompat.getInstance(mContext) - .isQuietModeEnabled(user); - for (int i = 0; i < count; i++) { - LauncherActivityInstallInfo info = apps.get(i); - AppInfo appInfo = new AppInfo(info.info, user, quietModeEnabled); - mIconCache.getTitleAndIcon(appInfo, info.info, false /* useLowResIcon */); - ShortcutInfo si = appInfo.makeShortcut(); - ((info.installTime <= folderCreationTime) ? workFolderApps : homescreenApps).add(si); - } - - finalizeWorkFolder(user, workFolderApps, homescreenApps); - - // Do not add shortcuts on the homescreen for the first time. This prevents the launcher - // getting filled with the managed user apps, when it start with a fresh DB (or after - // a very long time). - if (userAppsExisted && !homescreenApps.isEmpty() && mAddIconsToHomescreen) { - mModel.addAndBindAddedWorkspaceItems(new ArrayList(homescreenApps)); + InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_BULK_ADD); + for (LauncherActivityInfo app : apps) { + // Queue all items which should go in the work folder. + if (app.getFirstInstallTime() < ufi.addIconToFolderTime) { + InstallShortcutReceiver.queueActivityInfo(app, context); } } + // Post the queue update on next frame, so that the loader gets finished. + new Handler(LauncherModel.getWorkerLooper()).post(new Runnable() { + @Override + public void run() { + InstallShortcutReceiver.disableAndFlushInstallQueue( + InstallShortcutReceiver.FLAG_BULK_ADD, context); + } + }); + } - @Override - protected void onLauncherPackageRemoved(String packageName, UserHandle user) { + + /** + * Utility class to help workspace icon addition. + */ + public static class UserFolderInfo { + + final ArrayList pendingShortcuts = new ArrayList<>(); + + final UserHandle user; + + final long userSerial; + // Time until which icons will be added to folder instead. + final long addIconToFolderTime; + + final String folderIdKey; + final SharedPreferences prefs; + + final boolean folderAlreadyCreated; + final FolderInfo folderInfo; + + boolean folderPendingAddition; + + public UserFolderInfo(Context context, UserHandle user, BgDataModel dataModel) { + this.user = user; + + UserManagerCompat um = UserManagerCompat.getInstance(context); + userSerial = um.getSerialNumberForUser(user); + addIconToFolderTime = um.getUserCreationTime(user) + AUTO_ADD_TO_FOLDER_DURATION; + + folderIdKey = USER_FOLDER_ID_PREFIX + userSerial; + prefs = prefs(context); + + folderAlreadyCreated = prefs.contains(folderIdKey); + if (dataModel != null) { + if (folderAlreadyCreated) { + long folderId = prefs.getLong(folderIdKey, ItemInfo.NO_ID); + folderInfo = dataModel.folders.get(folderId); + } else { + folderInfo = new FolderInfo(); + folderInfo.title = context.getText(R.string.work_folder_name); + folderInfo.setOption(FolderInfo.FLAG_WORK_FOLDER, true, null); + folderPendingAddition = true; + } + } else { + folderInfo = null; + } } /** - * Adds and binds shortcuts marked to be added to the work folder. + * Returns the ItemInfo which should be added to the workspace. In case the the provided + * {@link ShortcutInfo} or a wrapped {@link FolderInfo} or null. */ - private void finalizeWorkFolder( - UserHandle user, final ArrayList workFolderApps, - ArrayList homescreenApps) { - if (workFolderApps.isEmpty()) { + public ItemInfo convertToWorkspaceItem( + ShortcutInfo shortcut, LauncherActivityInfo activityInfo) { + if (activityInfo.getFirstInstallTime() >= addIconToFolderTime) { + return shortcut; + } + + if (folderAlreadyCreated) { + if (folderInfo == null) { + // Work folder was deleted by user, add icon to home screen. + return shortcut; + } else { + // Add item to work folder instead. Nothing needs to be added + // on the homescreen. + pendingShortcuts.add(shortcut); + return null; + } + } + + pendingShortcuts.add(shortcut); + folderInfo.add(shortcut, false); + if (folderPendingAddition) { + folderPendingAddition = false; + return folderInfo; + } else { + // WorkFolder already requested to be added. Nothing new needs to be added. + return null; + } + } + + public void applyPendingState(ModelWriter writer) { + if (folderInfo == null) { return; } - // Try to get a work folder. - String folderIdKey = USER_FOLDER_ID_PREFIX + mUserManager.getSerialNumberForUser(user); - if (!mAddIconsToHomescreen) { - if (!mPrefs.contains(folderIdKey)) { - // Just mark the folder id preference to avoid new folder creation later. - mPrefs.edit().putLong(folderIdKey, -1).apply(); - } - return; + + int startingRank = 0; + if (folderAlreadyCreated) { + startingRank = folderInfo.contents.size(); } - if (mPrefs.contains(folderIdKey)) { - long folderId = mPrefs.getLong(folderIdKey, 0); - final FolderInfo workFolder = mModel.findFolderById(folderId); - if (workFolder == null || !workFolder.hasOption(FolderInfo.FLAG_WORK_FOLDER)) { - // Could not get a work folder. Add all the icons to homescreen. - homescreenApps.addAll(0, workFolderApps); - return; - } - saveWorkFolderShortcuts(folderId, workFolder.contents.size(), workFolderApps); + for (ShortcutInfo info : pendingShortcuts) { + info.rank = startingRank++; + writer.addItemToDatabase(info, folderInfo.id, 0, 0, 0); + } + if (folderAlreadyCreated) { // FolderInfo could already be bound. We need to add shortcuts on the UI thread. new MainThreadExecutor().execute(new Runnable() { @Override public void run() { - workFolder.prepareAutoUpdate(); - for (ShortcutInfo info : workFolderApps) { - workFolder.add(info, false); + folderInfo.prepareAutoUpdate(); + for (ShortcutInfo info : pendingShortcuts) { + folderInfo.add(info, false); } } }); } else { - // Create a new folder. - final FolderInfo workFolder = new FolderInfo(); - workFolder.title = mContext.getText(R.string.work_folder_name); - workFolder.setOption(FolderInfo.FLAG_WORK_FOLDER, true, null); - - // Add all shortcuts before adding it to the UI, as an empty folder might get deleted. - for (ShortcutInfo info : workFolderApps) { - workFolder.add(info, false); - } - - // Add the item to home screen and DB. This also generates an item id synchronously. - ArrayList itemList = new ArrayList<>(1); - itemList.add(workFolder); - mModel.addAndBindAddedWorkspaceItems(itemList); - mPrefs.edit().putLong(folderIdKey, workFolder.id).apply(); - - saveWorkFolderShortcuts(workFolder.id, 0, workFolderApps); + prefs.edit().putLong(folderIdKey, folderInfo.id).apply(); } } - - @Override - public void onShortcutsChanged(String packageName, List shortcuts, - UserHandle user) { - // Do nothing - } - } - - /** - * Add work folder shortcuts to the DB. - */ - private void saveWorkFolderShortcuts( - long workFolderId, int startingRank, ArrayList workFolderApps) { - for (ItemInfo info : workFolderApps) { - info.rank = startingRank++; - mModel.getWriter(false).addItemToDatabase(info, workFolderId, 0, 0, 0); - } } /** @@ -225,14 +209,12 @@ public class ManagedProfileHeuristic { */ public static void processAllUsers(List users, Context context) { UserManagerCompat userManager = UserManagerCompat.getInstance(context); - HashSet validKeys = new HashSet(); + HashSet validKeys = new HashSet<>(); for (UserHandle user : users) { - addAllUserKeys(userManager.getSerialNumberForUser(user), validKeys); + validKeys.add(USER_FOLDER_ID_PREFIX + userManager.getSerialNumberForUser(user)); } - SharedPreferences prefs = context.getSharedPreferences( - LauncherFiles.MANAGED_USER_PREFERENCES_KEY, - Context.MODE_PRIVATE); + SharedPreferences prefs = prefs(context); SharedPreferences.Editor editor = prefs.edit(); for (String key : prefs.getAll().keySet()) { if (!validKeys.contains(key)) { @@ -242,11 +224,6 @@ public class ManagedProfileHeuristic { editor.apply(); } - private static void addAllUserKeys(long userSerial, HashSet keysOut) { - keysOut.add(INSTALLED_PACKAGES_PREFIX + userSerial); - keysOut.add(USER_FOLDER_ID_PREFIX + userSerial); - } - /** * For each user, if a work folder has not been created, mark it such that the folder will * never get created. @@ -260,11 +237,8 @@ public class ManagedProfileHeuristic { if (myUser.equals(user)) { continue; } - if (prefs == null) { - prefs = context.getSharedPreferences( - LauncherFiles.MANAGED_USER_PREFERENCES_KEY, - Context.MODE_PRIVATE); + prefs = prefs(context); } String folderIdKey = USER_FOLDER_ID_PREFIX + userManager.getSerialNumberForUser(user); if (!prefs.contains(folderIdKey)) { @@ -272,4 +246,9 @@ public class ManagedProfileHeuristic { } } } + + public static SharedPreferences prefs(Context context) { + return context.getSharedPreferences( + LauncherFiles.MANAGED_USER_PREFERENCES_KEY, Context.MODE_PRIVATE); + } } diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java index 883be5aa3d..4c80902f00 100644 --- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java +++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java @@ -21,6 +21,7 @@ import org.mockito.ArgumentCaptor; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import static org.mockito.Mockito.any; import static org.mockito.Mockito.verify; @@ -50,7 +51,11 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { } private AddWorkspaceItemsTask newTask(ItemInfo... items) { - return new AddWorkspaceItemsTask(Provider.of(Arrays.asList(items))) { + List> list = new ArrayList<>(); + for (ItemInfo item : items) { + list.add(Pair.create(item, null)); + } + return new AddWorkspaceItemsTask(Provider.of(list)) { @Override protected void updateScreens(Context context, ArrayList workspaceScreens) { }