diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2016-09-09 15:47:55 -0700 |
---|---|---|
committer | Sunny Goyal <sunnygoyal@google.com> | 2016-11-09 15:50:08 -0800 |
commit | f0ba8b7ca1dc8fd53451d3d16e9f4fc306cddcd4 (patch) | |
tree | 821627bf354c858215e48c58cc5122a94ebd9c08 /src/com | |
parent | 0d547bfd570e2e5d6ced55e24305d070fe2b78e2 (diff) | |
download | android_packages_apps_Trebuchet-f0ba8b7ca1dc8fd53451d3d16e9f4fc306cddcd4.tar.gz android_packages_apps_Trebuchet-f0ba8b7ca1dc8fd53451d3d16e9f4fc306cddcd4.tar.bz2 android_packages_apps_Trebuchet-f0ba8b7ca1dc8fd53451d3d16e9f4fc306cddcd4.zip |
Moving various runnables in LauncherModel to individual tasks
> Adding tests for some of the runnable
Change-Id: I1a315d38878857df3371f0e69d622a41fc3b081a
Diffstat (limited to 'src/com')
14 files changed, 1214 insertions, 908 deletions
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java index 0e465a41e..b13c20b20 100644 --- a/src/com/android/launcher3/AllAppsList.java +++ b/src/com/android/launcher3/AllAppsList.java @@ -155,10 +155,9 @@ public class AllAppsList { // to the removed list. for (int i = data.size() - 1; i >= 0; i--) { final AppInfo applicationInfo = data.get(i); - final ComponentName component = applicationInfo.intent.getComponent(); if (user.equals(applicationInfo.user) - && packageName.equals(component.getPackageName())) { - if (!findActivity(matches, component)) { + && packageName.equals(applicationInfo.componentName.getPackageName())) { + if (!findActivity(matches, applicationInfo.componentName)) { removed.add(applicationInfo); data.remove(i); } @@ -182,11 +181,10 @@ public class AllAppsList { // Remove all data for this package. for (int i = data.size() - 1; i >= 0; i--) { final AppInfo applicationInfo = data.get(i); - final ComponentName component = applicationInfo.intent.getComponent(); if (user.equals(applicationInfo.user) - && packageName.equals(component.getPackageName())) { + && packageName.equals(applicationInfo.componentName.getPackageName())) { removed.add(applicationInfo); - mIconCache.remove(component, user); + mIconCache.remove(applicationInfo.componentName, user); data.remove(i); } } @@ -238,9 +236,8 @@ public class AllAppsList { private AppInfo findApplicationInfoLocked(String packageName, UserHandleCompat user, String className) { for (AppInfo info: data) { - final ComponentName component = info.intent.getComponent(); - if (user.equals(info.user) && packageName.equals(component.getPackageName()) - && className.equals(component.getClassName())) { + if (user.equals(info.user) && packageName.equals(info.componentName.getPackageName()) + && className.equals(info.componentName.getClassName())) { return info; } } diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index 661f99b9e..04d0c8c91 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -79,7 +79,7 @@ public class IconCache { @Thunk static final Object ICON_UPDATE_TOKEN = new Object(); - @Thunk static class CacheEntry { + public static class CacheEntry { public Bitmap icon; public CharSequence title = ""; public CharSequence contentDescription = ""; @@ -544,7 +544,7 @@ public class IconCache { * Retrieves the entry from the cache. If the entry is not present, it creates a new entry. * This method is not thread safe, it must be called from a synchronized method. */ - private CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info, + protected CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info, UserHandleCompat user, boolean usePackageIcon, boolean useLowResIcon) { ComponentKey cacheKey = new ComponentKey(componentName, user); CacheEntry entry = mCache.get(cacheKey); diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java index bd20e324b..b81074041 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -241,7 +241,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { // Add the new apps to the model and bind them if (!addShortcuts.isEmpty()) { LauncherAppState app = LauncherAppState.getInstance(); - app.getModel().addAndBindAddedWorkspaceItems(context, addShortcuts); + app.getModel().addAndBindAddedWorkspaceItems(addShortcuts); } } } diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java index 66d895726..78f5b8edb 100644 --- a/src/com/android/launcher3/LauncherAppWidgetInfo.java +++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java @@ -84,12 +84,12 @@ public class LauncherAppWidgetInfo extends ItemInfo { /** * Indicates the restore status of the widget. */ - int restoreStatus; + public int restoreStatus; /** * Indicates the installation progress of the widget provider */ - int installProgress = -1; + public int installProgress = -1; /** * Optional extras sent during widget bind. See {@link #FLAG_DIRECT_CONFIG}. @@ -98,7 +98,7 @@ public class LauncherAppWidgetInfo extends ItemInfo { private boolean mHasNotifiedInitialWidgetSizeChanged; - LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) { + public LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) { if (appWidgetId == CUSTOM_WIDGET_ID) { itemType = LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; } else { @@ -117,6 +117,11 @@ public class LauncherAppWidgetInfo extends ItemInfo { restoreStatus = RESTORE_COMPLETED; } + /** Used for testing **/ + public LauncherAppWidgetInfo() { + itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; + } + public boolean isCustomWidget() { return appWidgetId == CUSTOM_WIDGET_ID; } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 955f51f5f..c70a47595 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -27,7 +27,6 @@ import android.content.Intent; import android.content.Intent.ShortcutIconResource; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; @@ -43,7 +42,6 @@ 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.LauncherActivityInfoCompat; @@ -59,11 +57,18 @@ import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.logging.FileLog; +import com.android.launcher3.model.AddWorkspaceItemsTask; +import com.android.launcher3.model.ExtendedModelTask; import com.android.launcher3.model.BgDataModel; +import com.android.launcher3.model.CacheDataUpdatedTask; import com.android.launcher3.model.GridSizeMigrationTask; import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.model.SdCardAvailableReceiver; import com.android.launcher3.model.WidgetItem; +import com.android.launcher3.model.PackageInstallStateChangedTask; +import com.android.launcher3.model.PackageUpdatedTask; +import com.android.launcher3.model.ShortcutsChangedTask; +import com.android.launcher3.model.UserLockStateChangedTask; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.provider.ImportDataTask; import com.android.launcher3.provider.LauncherDbUtils; @@ -72,7 +77,6 @@ import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.CursorIconInfo; -import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.LongArrayMap; @@ -87,7 +91,6 @@ import java.lang.ref.WeakReference; import java.net.URISyntaxException; import java.security.InvalidParameterException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -168,8 +171,8 @@ public class LauncherModel extends BroadcastReceiver // </ only access in worker thread > - private IconCache mIconCache; - private DeepShortcutManager mDeepShortcutManager; + private final IconCache mIconCache; + private final DeepShortcutManager mDeepShortcutManager; private final LauncherAppsCompat mLauncherApps; private final UserManagerCompat mUserManager; @@ -241,286 +244,26 @@ public class LauncherModel extends BroadcastReceiver } } - public void setPackageState(final PackageInstallInfo installInfo) { - Runnable updateRunnable = new Runnable() { - - @Override - public void run() { - synchronized (sBgDataModel) { - final HashSet<ItemInfo> updates = new HashSet<>(); - - if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) { - // Ignore install success events as they are handled by Package add events. - return; - } - - for (ItemInfo info : sBgDataModel.itemsIdMap) { - if (info instanceof ShortcutInfo) { - ShortcutInfo si = (ShortcutInfo) info; - ComponentName cn = si.getTargetComponent(); - if (si.isPromise() && (cn != null) - && installInfo.packageName.equals(cn.getPackageName())) { - si.setInstallProgress(installInfo.progress); - - if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) { - // Mark this info as broken. - si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE; - } - updates.add(si); - } - } - } - - for (LauncherAppWidgetInfo widget : sBgDataModel.appWidgets) { - if (widget.providerName.getPackageName().equals(installInfo.packageName)) { - widget.installProgress = installInfo.progress; - updates.add(widget); - } - } - - if (!updates.isEmpty()) { - // Push changes to the callback. - Runnable r = new Runnable() { - public void run() { - Callbacks callbacks = getCallback(); - if (callbacks != null) { - callbacks.bindRestoreItemsChange(updates); - } - } - }; - mHandler.post(r); - } - } - } - }; - runOnWorkerThread(updateRunnable); + public void setPackageState(PackageInstallInfo installInfo) { + enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo)); } /** * Updates the icons and label of all pending icons for the provided package name. */ public void updateSessionDisplayInfo(final String packageName) { - Runnable updateRunnable = new Runnable() { - - @Override - public void run() { - synchronized (sBgDataModel) { - ArrayList<ShortcutInfo> updates = new ArrayList<>(); - UserHandleCompat user = UserHandleCompat.myUserHandle(); - - for (ItemInfo info : sBgDataModel.itemsIdMap) { - if (info instanceof ShortcutInfo) { - ShortcutInfo si = (ShortcutInfo) info; - ComponentName cn = si.getTargetComponent(); - if (si.isPromise() && (cn != null) - && packageName.equals(cn.getPackageName())) { - si.updateIcon(mIconCache); - updates.add(si); - } - } - } - - bindUpdatedShortcuts(updates, user); - } - } - }; - runOnWorkerThread(updateRunnable); - } - - public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) { - final Callbacks callbacks = getCallback(); - - if (allAppsApps == null) { - throw new RuntimeException("allAppsApps must not be null"); - } - if (allAppsApps.isEmpty()) { - return; - } - - // Process the newly added applications and add them to the database first - Runnable r = new Runnable() { - public void run() { - runOnMainThread(new Runnable() { - public void run() { - Callbacks cb = getCallback(); - if (callbacks == cb && cb != null) { - callbacks.bindAppsAdded(null, null, null, allAppsApps); - } - } - }); - } - }; - runOnWorkerThread(r); - } - - private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos, - int[] xy, int spanX, int spanY) { - LauncherAppState app = LauncherAppState.getInstance(); - InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); - - GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows); - if (occupiedPos != null) { - for (ItemInfo r : occupiedPos) { - occupied.markCells(r, true); - } - } - return occupied.findVacantCell(xy, spanX, spanY); - } - - /** - * Find a position on the screen for the given size or adds a new screen. - * @return screenId and the coordinates for the item. - */ - @Thunk Pair<Long, int[]> findSpaceForItem( - Context context, - ArrayList<Long> workspaceScreens, - ArrayList<Long> addedWorkspaceScreensFinal, - int spanX, int spanY) { - LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>(); - - // Use sBgItemsIdMap as all the items are already loaded. - assertWorkspaceLoaded(); - synchronized (sBgDataModel) { - for (ItemInfo info : sBgDataModel.itemsIdMap) { - if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { - ArrayList<ItemInfo> items = screenItems.get(info.screenId); - if (items == null) { - items = new ArrayList<>(); - screenItems.put(info.screenId, items); - } - items.add(info); - } - } - } - - // Find appropriate space for the item. - long screenId = 0; - int[] cordinates = new int[2]; - boolean found = false; - - int screenCount = workspaceScreens.size(); - // First check the preferred screen. - int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1; - if (preferredScreenIndex < screenCount) { - screenId = workspaceScreens.get(preferredScreenIndex); - found = findNextAvailableIconSpaceInScreen( - screenItems.get(screenId), cordinates, spanX, spanY); - } - - if (!found) { - // Search on any of the screens starting from the first screen. - for (int screen = 1; screen < screenCount; screen++) { - screenId = workspaceScreens.get(screen); - if (findNextAvailableIconSpaceInScreen( - screenItems.get(screenId), cordinates, spanX, spanY)) { - // We found a space for it - found = true; - break; - } - } - } - - if (!found) { - // Still no position found. Add a new screen to the end. - screenId = LauncherSettings.Settings.call(context.getContentResolver(), - LauncherSettings.Settings.METHOD_NEW_SCREEN_ID) - .getLong(LauncherSettings.Settings.EXTRA_VALUE); - - // Save the screen id for binding in the workspace - workspaceScreens.add(screenId); - addedWorkspaceScreensFinal.add(screenId); - - // If we still can't find an empty space, then God help us all!!! - if (!findNextAvailableIconSpaceInScreen( - screenItems.get(screenId), cordinates, spanX, spanY)) { - throw new RuntimeException("Can't find space to add the item"); - } - } - return Pair.create(screenId, cordinates); + HashSet<String> packages = new HashSet<>(); + packages.add(packageName); + enqueueModelUpdateTask(new CacheDataUpdatedTask( + CacheDataUpdatedTask.OP_SESSION_UPDATE, UserHandleCompat.myUserHandle(), packages)); } /** * Adds the provided items to the workspace. */ - public void addAndBindAddedWorkspaceItems(final Context context, + public void addAndBindAddedWorkspaceItems( final ArrayList<? extends ItemInfo> workspaceApps) { - final Callbacks callbacks = getCallback(); - if (workspaceApps.isEmpty()) { - return; - } - // Process the newly added applications and add them to the database first - Runnable r = new Runnable() { - public void run() { - final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>(); - final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>(); - - // 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<Long> workspaceScreens = loadWorkspaceScreensDb(context); - synchronized(sBgDataModel) { - for (ItemInfo item : workspaceApps) { - if (item instanceof ShortcutInfo) { - // Short-circuit this logic if the icon exists somewhere on the workspace - if (shortcutExists(context, item.getIntent(), item.user)) { - continue; - } - } - - // Find appropriate space for the item. - Pair<Long, int[]> coords = findSpaceForItem(context, - workspaceScreens, addedWorkspaceScreensFinal, 1, 1); - long screenId = coords.first; - int[] cordinates = coords.second; - - ItemInfo itemInfo; - if (item instanceof ShortcutInfo || item instanceof FolderInfo) { - itemInfo = item; - } else if (item instanceof AppInfo) { - itemInfo = ((AppInfo) item).makeShortcut(); - } else { - throw new RuntimeException("Unexpected info type"); - } - - // Add the shortcut to the db - addItemToDatabase(context, itemInfo, - LauncherSettings.Favorites.CONTAINER_DESKTOP, - screenId, cordinates[0], cordinates[1]); - // Save the ShortcutInfo for binding in the workspace - addedShortcutsFinal.add(itemInfo); - } - } - - // Update the workspace screens - updateWorkspaceScreenOrder(context, workspaceScreens); - - if (!addedShortcutsFinal.isEmpty()) { - runOnMainThread(new Runnable() { - public void run() { - Callbacks cb = getCallback(); - if (callbacks == cb && cb != null) { - final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>(); - final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>(); - if (!addedShortcutsFinal.isEmpty()) { - ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1); - long lastScreenId = info.screenId; - for (ItemInfo i : addedShortcutsFinal) { - if (i.screenId == lastScreenId) { - addAnimated.add(i); - } else { - addNotAnimated.add(i); - } - } - } - callbacks.bindAppsAdded(addedWorkspaceScreensFinal, - addNotAnimated, addAnimated, null); - } - } - }); - } - } - }; - runOnWorkerThread(r); + enqueueModelUpdateTask(new AddWorkspaceItemsTask(workspaceApps)); } /** @@ -784,60 +527,6 @@ public class LauncherModel extends BroadcastReceiver updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase"); } - private void assertWorkspaceLoaded() { - if (ProviderConfig.IS_DOGFOOD_BUILD) { - synchronized (mLock) { - if (!mHasLoaderCompletedOnce || - (mLoaderTask != null && mLoaderTask.mIsLoadingAndBindingWorkspace)) { - throw new RuntimeException("Trying to add shortcut while loader is running"); - } - } - } - } - - /** - * Returns true if the shortcuts already exists on the workspace. This must be called after - * the workspace has been loaded. We identify a shortcut by its intent. - */ - @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) { - assertWorkspaceLoaded(); - final String intentWithPkg, intentWithoutPkg; - if (intent.getComponent() != null) { - // If component is not null, an intent with null package will produce - // the same result and should also be a match. - String packageName = intent.getComponent().getPackageName(); - if (intent.getPackage() != null) { - intentWithPkg = intent.toUri(0); - intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0); - } else { - intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0); - intentWithoutPkg = intent.toUri(0); - } - } else { - intentWithPkg = intent.toUri(0); - intentWithoutPkg = intent.toUri(0); - } - - synchronized (sBgDataModel) { - for (ItemInfo item : sBgDataModel.itemsIdMap) { - if (item instanceof ShortcutInfo) { - ShortcutInfo info = (ShortcutInfo) item; - Intent targetIntent = info.promisedIntent == null - ? info.intent : info.promisedIntent; - if (targetIntent != null && info.user.equals(user)) { - Intent copyIntent = new Intent(targetIntent); - copyIntent.setSourceBounds(intent.getSourceBounds()); - String s = copyIntent.toUri(0); - if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) { - return true; - } - } - } - } - } - return false; - } - /** * Add an item to the database in a specified container. Sets the container, screen, cellX and * cellY fields of the item. Also assigns an ID to the item. @@ -899,7 +588,8 @@ public class LauncherModel extends BroadcastReceiver /** * Removes the specified items from the database */ - static void deleteItemsFromDatabase(Context context, final Iterable<? extends ItemInfo> items) { + public static void deleteItemsFromDatabase(Context context, + final Iterable<? extends ItemInfo> items) { final ContentResolver cr = context.getContentResolver(); Runnable r = new Runnable() { public void run() { @@ -918,7 +608,7 @@ public class LauncherModel extends BroadcastReceiver * Update the order of the workspace screens in the database. The array list contains * a list of screen ids in the order that they should appear. */ - public void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) { + public static void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) { final ArrayList<Long> screensCopy = new ArrayList<Long>(screens); final ContentResolver cr = context.getContentResolver(); final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI; @@ -997,8 +687,7 @@ public class LauncherModel extends BroadcastReceiver @Override public void onPackageChanged(String packageName, UserHandleCompat user) { int op = PackageUpdatedTask.OP_UPDATE; - enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName }, - user)); + enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName)); } @Override @@ -1008,56 +697,52 @@ public class LauncherModel extends BroadcastReceiver public void onPackagesRemoved(UserHandleCompat user, String... packages) { int op = PackageUpdatedTask.OP_REMOVE; - enqueueItemUpdatedTask(new PackageUpdatedTask(op, packages, user)); + enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages)); } @Override public void onPackageAdded(String packageName, UserHandleCompat user) { int op = PackageUpdatedTask.OP_ADD; - enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName }, - user)); + enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName)); } @Override public void onPackagesAvailable(String[] packageNames, UserHandleCompat user, boolean replacing) { - enqueueItemUpdatedTask( - new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, packageNames, user)); + enqueueModelUpdateTask( + new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames)); } @Override public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user, boolean replacing) { if (!replacing) { - enqueueItemUpdatedTask(new PackageUpdatedTask( - PackageUpdatedTask.OP_UNAVAILABLE, packageNames, - user)); + enqueueModelUpdateTask(new PackageUpdatedTask( + PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames)); } } @Override public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) { - enqueueItemUpdatedTask(new PackageUpdatedTask( - PackageUpdatedTask.OP_SUSPEND, packageNames, - user)); + enqueueModelUpdateTask(new PackageUpdatedTask( + PackageUpdatedTask.OP_SUSPEND, user, packageNames)); } @Override public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) { - enqueueItemUpdatedTask(new PackageUpdatedTask( - PackageUpdatedTask.OP_UNSUSPEND, packageNames, - user)); + enqueueModelUpdateTask(new PackageUpdatedTask( + PackageUpdatedTask.OP_UNSUSPEND, user, packageNames)); } @Override public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts, UserHandleCompat user) { - enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user, true)); + enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true)); } public void updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts, UserHandleCompat user) { - enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user, false)); + enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false)); } /** @@ -1083,16 +768,15 @@ public class LauncherModel extends BroadcastReceiver if (user != null) { if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) { - enqueueItemUpdatedTask(new PackageUpdatedTask( - PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, - new String[0], user)); + enqueueModelUpdateTask(new PackageUpdatedTask( + PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)); } // ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so // we need to run the state change task again. if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) || Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) { - enqueueItemUpdatedTask(new UserLockStateChangedTask(user)); + enqueueModelUpdateTask(new UserLockStateChangedTask(user)); } } } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) { @@ -2702,397 +2386,68 @@ public class LauncherModel extends BroadcastReceiver * Called when the icons for packages have been updated in the icon cache. */ public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) { - final Callbacks callbacks = getCallback(); - final ArrayList<AppInfo> updatedApps = new ArrayList<>(); - final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>(); - // If any package icon has changed (app was updated while launcher was dead), // update the corresponding shortcuts. - synchronized (sBgDataModel) { - for (ItemInfo info : sBgDataModel.itemsIdMap) { - if (info instanceof ShortcutInfo && user.equals(info.user) - && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - ShortcutInfo si = (ShortcutInfo) info; - ComponentName cn = si.getTargetComponent(); - if (cn != null && updatedPackages.contains(cn.getPackageName())) { - si.updateIcon(mIconCache); - updatedShortcuts.add(si); - } - } - } - mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps); - } - - bindUpdatedShortcuts(updatedShortcuts, user); - - if (!updatedApps.isEmpty()) { - mHandler.post(new Runnable() { - - public void run() { - Callbacks cb = getCallback(); - if (cb != null && callbacks == cb) { - cb.bindAppsUpdated(updatedApps); - } - } - }); - } + enqueueModelUpdateTask(new CacheDataUpdatedTask( + CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages)); } - private void bindUpdatedShortcuts( - ArrayList<ShortcutInfo> updatedShortcuts, UserHandleCompat user) { - bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user); + void enqueueModelUpdateTask(BaseModelUpdateTask task) { + task.init(this); + runOnWorkerThread(task); } - private void bindUpdatedShortcuts( - final ArrayList<ShortcutInfo> updatedShortcuts, - final ArrayList<ShortcutInfo> removedShortcuts, - final UserHandleCompat user) { - if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) { - final Callbacks callbacks = getCallback(); - mHandler.post(new Runnable() { - - public void run() { - Callbacks cb = getCallback(); - if (cb != null && callbacks == cb) { - cb.bindShortcutsChanged(updatedShortcuts, removedShortcuts, user); - } - } - }); - } - } + /** + * A task to be executed on the current callbacks on the UI thread. + * If there is no current callbacks, the task is ignored. + */ + public interface CallbackTask { - void enqueueItemUpdatedTask(Runnable task) { - sWorker.post(task); + void execute(Callbacks callbacks); } - private class PackageUpdatedTask implements Runnable { - final int mOp; - final String[] mPackages; - final UserHandleCompat mUser; + /** + * A runnable which changes/updates the data model of the launcher based on certain events. + */ + public static abstract class BaseModelUpdateTask implements Runnable { - public static final int OP_NONE = 0; - public static final int OP_ADD = 1; - public static final int OP_UPDATE = 2; - public static final int OP_REMOVE = 3; // uninstlled - public static final int OP_UNAVAILABLE = 4; // external media unmounted - public static final int OP_SUSPEND = 5; // package suspended - public static final int OP_UNSUSPEND = 6; // package unsuspended - public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable + private LauncherModel mModel; + private DeferredHandler mUiHandler; - public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) { - mOp = op; - mPackages = packages; - mUser = user; + /* package private */ + void init(LauncherModel model) { + mModel = model; + mUiHandler = mModel.mHandler; } + @Override public void run() { - if (!mHasLoaderCompletedOnce) { + if (!mModel.mHasLoaderCompletedOnce) { // Loader has not yet run. return; } - final Context context = mApp.getContext(); - - final String[] packages = mPackages; - final int N = packages.length; - FlagOp flagOp = FlagOp.NO_OP; - final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages)); - switch (mOp) { - case OP_ADD: { - for (int i=0; i<N; i++) { - if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]); - mIconCache.updateIconsForPkg(packages[i], mUser); - mBgAllAppsList.addPackage(context, packages[i], mUser); - } - - ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser); - if (heuristic != null) { - heuristic.processPackageAdd(mPackages); - } - break; - } - case OP_UPDATE: - for (int i=0; i<N; i++) { - if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]); - mIconCache.updateIconsForPkg(packages[i], mUser); - mBgAllAppsList.updatePackage(context, packages[i], mUser); - mApp.getWidgetCache().removePackage(packages[i], mUser); - } - // Since package was just updated, the target must be available now. - 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++) { - if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]); - mIconCache.removeIconsForPkg(packages[i], mUser); - } - // Fall through - } - case OP_UNAVAILABLE: - for (int i=0; i<N; i++) { - if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]); - mBgAllAppsList.removePackage(packages[i], mUser); - mApp.getWidgetCache().removePackage(packages[i], mUser); - } - flagOp = FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE); - break; - case OP_SUSPEND: - case OP_UNSUSPEND: - flagOp = mOp == OP_SUSPEND ? - FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED) : - FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED); - if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.(un)suspend " + N); - mBgAllAppsList.updateDisabledFlags( - ItemInfoMatcher.ofPackages(packageSet, mUser), flagOp); - break; - case OP_USER_AVAILABILITY_CHANGE: - flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser) - ? FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER) - : FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER); - // We want to update all packages for this user. - mBgAllAppsList.updateDisabledFlags(ItemInfoMatcher.ofUser(mUser), flagOp); - break; - } - - ArrayList<AppInfo> added = null; - ArrayList<AppInfo> modified = null; - final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>(); - - if (mBgAllAppsList.added.size() > 0) { - added = new ArrayList<>(mBgAllAppsList.added); - mBgAllAppsList.added.clear(); - } - if (mBgAllAppsList.modified.size() > 0) { - modified = new ArrayList<>(mBgAllAppsList.modified); - mBgAllAppsList.modified.clear(); - } - if (mBgAllAppsList.removed.size() > 0) { - removedApps.addAll(mBgAllAppsList.removed); - mBgAllAppsList.removed.clear(); - } - - final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<>(); - - if (added != null) { - addAppsToAllApps(context, added); - for (AppInfo ai : added) { - addedOrUpdatedApps.put(ai.componentName, ai); - } - } - - if (modified != null) { - final Callbacks callbacks = getCallback(); - final ArrayList<AppInfo> modifiedFinal = modified; - for (AppInfo ai : modified) { - addedOrUpdatedApps.put(ai.componentName, ai); - } - - mHandler.post(new Runnable() { - public void run() { - Callbacks cb = getCallback(); - if (callbacks == cb && cb != null) { - callbacks.bindAppsUpdated(modifiedFinal); - } - } - }); - } - - // Update shortcut infos - if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) { - final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>(); - final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<>(); - final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>(); - - synchronized (sBgDataModel) { - for (ItemInfo info : sBgDataModel.itemsIdMap) { - if (info instanceof ShortcutInfo && mUser.equals(info.user)) { - ShortcutInfo si = (ShortcutInfo) info; - boolean infoUpdated = false; - boolean shortcutUpdated = false; - - // Update shortcuts which use iconResource. - if ((si.iconResource != null) - && packageSet.contains(si.iconResource.packageName)) { - Bitmap icon = LauncherIcons.createIconBitmap( - si.iconResource.packageName, - si.iconResource.resourceName, context); - if (icon != null) { - si.setIcon(icon); - si.usingFallbackIcon = false; - infoUpdated = true; - } - } - - ComponentName cn = si.getTargetComponent(); - if (cn != null && packageSet.contains(cn.getPackageName())) { - AppInfo appInfo = addedOrUpdatedApps.get(cn); - - if (si.isPromise()) { - if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) { - // Auto install icon - PackageManager pm = context.getPackageManager(); - ResolveInfo matched = pm.resolveActivity( - new Intent(Intent.ACTION_MAIN) - .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER), - PackageManager.MATCH_DEFAULT_ONLY); - if (matched == null) { - // Try to find the best match activity. - Intent intent = pm.getLaunchIntentForPackage( - cn.getPackageName()); - if (intent != null) { - cn = intent.getComponent(); - appInfo = addedOrUpdatedApps.get(cn); - } - - if ((intent == null) || (appInfo == null)) { - removedShortcuts.add(si); - continue; - } - si.promisedIntent = intent; - } - } - - si.intent = si.promisedIntent; - si.promisedIntent = null; - si.status = ShortcutInfo.DEFAULT; - infoUpdated = true; - si.updateIcon(mIconCache); - } - - if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction()) - && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - si.updateIcon(mIconCache); - si.title = Utilities.trim(appInfo.title); - si.contentDescription = appInfo.contentDescription; - infoUpdated = true; - } - - int oldDisabledFlags = si.isDisabled; - si.isDisabled = flagOp.apply(si.isDisabled); - if (si.isDisabled != oldDisabledFlags) { - shortcutUpdated = true; - } - } - - if (infoUpdated || shortcutUpdated) { - updatedShortcuts.add(si); - } - if (infoUpdated) { - updateItemInDatabase(context, si); - } - } else if (info instanceof LauncherAppWidgetInfo && mOp == OP_ADD) { - LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info; - if (mUser.equals(widgetInfo.user) - && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) - && packageSet.contains(widgetInfo.providerName.getPackageName())) { - widgetInfo.restoreStatus &= - ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY & - ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; - - // adding this flag ensures that launcher shows 'click to setup' - // if the widget has a config activity. In case there is no config - // activity, it will be marked as 'restored' during bind. - widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY; - - widgets.add(widgetInfo); - updateItemInDatabase(context, widgetInfo); - } - } - } - } - - bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser); - if (!removedShortcuts.isEmpty()) { - deleteItemsFromDatabase(context, removedShortcuts); - } - - if (!widgets.isEmpty()) { - final Callbacks callbacks = getCallback(); - mHandler.post(new Runnable() { - public void run() { - Callbacks cb = getCallback(); - if (callbacks == cb && cb != null) { - callbacks.bindWidgetsRestored(widgets); - } - } - }); - } - } + execute(mModel.mApp, sBgDataModel, mModel.mBgAllAppsList); + } - final HashSet<String> removedPackages = new HashSet<>(); - final HashSet<ComponentName> removedComponents = new HashSet<>(); - if (mOp == OP_REMOVE) { - // Mark all packages in the broadcast to be removed - Collections.addAll(removedPackages, packages); + /** + * Execute the actual task. Called on the worker thread. + */ + public abstract void execute( + LauncherAppState app, BgDataModel dataModel, AllAppsList apps); - // No need to update the removedComponents as - // removedPackages is a super-set of removedComponents - } else if (mOp == OP_UPDATE) { - // Mark disabled packages in the broadcast to be removed - for (int i=0; i<N; i++) { - if (isPackageDisabled(context, packages[i], mUser)) { - removedPackages.add(packages[i]); + /** + * Schedules a {@param task} to be executed on the current callbacks. + */ + public final void scheduleCallbackTask(final CallbackTask task) { + final Callbacks callbacks = mModel.getCallback(); + mUiHandler.post(new Runnable() { + public void run() { + Callbacks cb = mModel.getCallback(); + if (callbacks == cb && cb != null) { + task.execute(callbacks); } } - - // Update removedComponents as some components can get removed during package update - for (AppInfo info : removedApps) { - removedComponents.add(info.componentName); - } - } - - if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) { - deleteItemsFromDatabase( - context, ItemInfoMatcher.ofPackages(removedPackages, mUser)); - deleteItemsFromDatabase( - context, ItemInfoMatcher.ofComponents(removedComponents, mUser)); - - // Remove any queued items from the install queue - InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser); - - // Call the components-removed callback - final Callbacks callbacks = getCallback(); - mHandler.post(new Runnable() { - public void run() { - Callbacks cb = getCallback(); - if (callbacks == cb && cb != null) { - callbacks.bindWorkspaceComponentsRemoved( - removedPackages, removedComponents, mUser); - } - } - }); - } - - if (!removedApps.isEmpty()) { - // Remove corresponding apps from All-Apps - final Callbacks callbacks = getCallback(); - mHandler.post(new Runnable() { - public void run() { - Callbacks cb = getCallback(); - if (callbacks == cb && cb != null) { - callbacks.bindAppInfosRemoved(removedApps); - } - } - }); - } - - // Notify launcher of widget update. From marshmallow onwards we use AppWidgetHost to - // get widget update signals. - if (!Utilities.ATLEAST_MARSHMALLOW && - (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE)) { - final Callbacks callbacks = getCallback(); - mHandler.post(new Runnable() { - public void run() { - Callbacks cb = getCallback(); - if (callbacks == cb && cb != null) { - callbacks.notifyWidgetProvidersChanged(); - } - } - }); - } + }); } } @@ -3100,171 +2455,18 @@ public class LauncherModel extends BroadcastReceiver * Repopulates the shortcut info, possibly updating any icon already on the workspace. */ public void updateShortcutInfo(final ShortcutInfoCompat fullDetail, final ShortcutInfo info) { - enqueueItemUpdatedTask(new Runnable() { + enqueueModelUpdateTask(new ExtendedModelTask() { @Override - public void run() { - info.updateFromDeepShortcutInfo( - fullDetail, LauncherAppState.getInstance().getContext()); - ArrayList<ShortcutInfo> update = new ArrayList<ShortcutInfo>(); + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + info.updateFromDeepShortcutInfo(fullDetail, app.getContext()); + + ArrayList<ShortcutInfo> update = new ArrayList<>(); update.add(info); bindUpdatedShortcuts(update, fullDetail.getUserHandle()); } }); } - private class ShortcutsChangedTask implements Runnable { - private final String mPackageName; - private final List<ShortcutInfoCompat> mShortcuts; - private final UserHandleCompat mUser; - private final boolean mUpdateIdMap; - - public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts, - UserHandleCompat user, boolean updateIdMap) { - mPackageName = packageName; - mShortcuts = shortcuts; - mUser = user; - mUpdateIdMap = updateIdMap; - } - - @Override - public void run() { - mDeepShortcutManager.onShortcutsChanged(mShortcuts); - - // Find ShortcutInfo's that have changed on the workspace. - final ArrayList<ShortcutInfo> removedShortcutInfos = new ArrayList<>(); - MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>(); - for (ItemInfo itemInfo : sBgDataModel.itemsIdMap) { - if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { - ShortcutInfo si = (ShortcutInfo) itemInfo; - if (si.getPromisedIntent().getPackage().equals(mPackageName) - && si.user.equals(mUser)) { - idsToWorkspaceShortcutInfos.addToList(si.getDeepShortcutId(), si); - } - } - } - - final Context context = LauncherAppState.getInstance().getContext(); - final ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>(); - if (!idsToWorkspaceShortcutInfos.isEmpty()) { - // Update the workspace to reflect the changes to updated shortcuts residing on it. - List<ShortcutInfoCompat> shortcuts = mDeepShortcutManager.queryForFullDetails( - mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser); - for (ShortcutInfoCompat fullDetails : shortcuts) { - List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos - .remove(fullDetails.getId()); - if (!fullDetails.isPinned()) { - // The shortcut was previously pinned but is no longer, so remove it from - // the workspace and our pinned shortcut counts. - // Note that we put this check here, after querying for full details, - // because there's a possible race condition between pinning and - // receiving this callback. - removedShortcutInfos.addAll(shortcutInfos); - continue; - } - for (ShortcutInfo shortcutInfo : shortcutInfos) { - shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context); - updatedShortcutInfos.add(shortcutInfo); - } - } - } - - // If there are still entries in idsToWorkspaceShortcutInfos, that means that - // the corresponding shortcuts weren't passed in onShortcutsChanged(). This - // means they were cleared, so we remove and unpin them now. - for (String id : idsToWorkspaceShortcutInfos.keySet()) { - removedShortcutInfos.addAll(idsToWorkspaceShortcutInfos.get(id)); - } - - bindUpdatedShortcuts(updatedShortcutInfos, removedShortcutInfos, mUser); - if (!removedShortcutInfos.isEmpty()) { - deleteItemsFromDatabase(context, removedShortcutInfos); - } - - if (mUpdateIdMap) { - // Update the deep shortcut map if the list of ids has changed for an activity. - sBgDataModel.updateDeepShortcutMap(mPackageName, mUser, mShortcuts); - bindDeepShortcuts(); - } - } - } - - /** - * Task to handle changing of lock state of the user - */ - private class UserLockStateChangedTask implements Runnable { - - private final UserHandleCompat mUser; - - public UserLockStateChangedTask(UserHandleCompat user) { - mUser = user; - } - - @Override - public void run() { - boolean isUserUnlocked = mUserManager.isUserUnlocked(mUser); - Context context = mApp.getContext(); - - HashMap<ShortcutKey, ShortcutInfoCompat> pinnedShortcuts = new HashMap<>(); - if (isUserUnlocked) { - List<ShortcutInfoCompat> shortcuts = - mDeepShortcutManager.queryForPinnedShortcuts(null, mUser); - if (mDeepShortcutManager.wasLastCallSuccess()) { - for (ShortcutInfoCompat shortcut : shortcuts) { - pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut); - } - } else { - // Shortcut manager can fail due to some race condition when the lock state - // changes too frequently. For the purpose of the update, - // consider it as still locked. - isUserUnlocked = false; - } - } - - // Update the workspace to reflect the changes to updated shortcuts residing on it. - ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>(); - ArrayList<ShortcutInfo> deletedShortcutInfos = new ArrayList<>(); - for (ItemInfo itemInfo : sBgDataModel.itemsIdMap) { - if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT - && mUser.equals(itemInfo.user)) { - ShortcutInfo si = (ShortcutInfo) itemInfo; - if (isUserUnlocked) { - ShortcutInfoCompat shortcut = - pinnedShortcuts.get(ShortcutKey.fromShortcutInfo(si)); - // We couldn't verify the shortcut during loader. If its no longer available - // (probably due to clear data), delete the workspace item as well - if (shortcut == null) { - deletedShortcutInfos.add(si); - continue; - } - si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_LOCKED_USER; - si.updateFromDeepShortcutInfo(shortcut, context); - } else { - si.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER; - } - updatedShortcutInfos.add(si); - } - } - bindUpdatedShortcuts(updatedShortcutInfos, deletedShortcutInfos, mUser); - if (!deletedShortcutInfos.isEmpty()) { - deleteItemsFromDatabase(context, deletedShortcutInfos); - } - - // Remove shortcut id map for that user - Iterator<ComponentKey> keysIter = sBgDataModel.deepShortcutMap.keySet().iterator(); - while (keysIter.hasNext()) { - if (keysIter.next().user.equals(mUser)) { - keysIter.remove(); - } - } - - if (isUserUnlocked) { - sBgDataModel.updateDeepShortcutMap( - null, mUser, mDeepShortcutManager.queryForAllShortcuts(mUser)); - } - bindDeepShortcuts(); - } - } - private void bindWidgetsModel(final Callbacks callbacks) { final MultiHashMap<PackageItemInfo, WidgetItem> widgets = mBgWidgetsModel.getWidgetsMap().clone(); @@ -3296,12 +2498,6 @@ public class LauncherModel extends BroadcastReceiver }); } - @Thunk static boolean isPackageDisabled(Context context, String packageName, - UserHandleCompat user) { - final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); - return !launcherApps.isPackageEnabledForProfile(packageName, user); - } - /** * Make an ShortcutInfo object for a restored application or shortcut item that points * to a package that is not yet installed on the system. diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index 9a9287234..fc087363a 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -80,7 +80,7 @@ public class ShortcutInfo extends ItemInfo { * Indicates whether we're using the default fallback icon instead of something from the * app. */ - boolean usingFallbackIcon; + public boolean usingFallbackIcon; /** * Indicates whether we're using a low res icon @@ -132,7 +132,7 @@ public class ShortcutInfo extends ItemInfo { * Could be disabled, if the the app is installed but unavailable (eg. in safe mode or when * sd-card is not available). */ - int isDisabled = DEFAULT; + public int isDisabled = DEFAULT; /** * A message to display when the user tries to start a disabled shortcut. @@ -140,7 +140,7 @@ public class ShortcutInfo extends ItemInfo { */ CharSequence disabledMessage; - int status; + public int status; /** * The installation progress [0-100] of the package that this shortcut represents. @@ -152,7 +152,7 @@ public class ShortcutInfo extends ItemInfo { * this will hold the original intent from the database. Otherwise, null. * Refer {@link #FLAG_RESTORED_ICON}, {@link #FLAG_AUTOINTALL_ICON} */ - Intent promisedIntent; + public Intent promisedIntent; public ShortcutInfo() { itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT; diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java new file mode 100644 index 000000000..986e163e6 --- /dev/null +++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java @@ -0,0 +1,262 @@ +/* + * 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.model; + +import android.content.Context; +import android.content.Intent; +import android.util.LongSparseArray; +import android.util.Pair; + +import com.android.launcher3.AllAppsList; +import com.android.launcher3.AppInfo; +import com.android.launcher3.FolderInfo; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherModel; +import com.android.launcher3.LauncherModel.CallbackTask; +import com.android.launcher3.LauncherModel.Callbacks; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.util.GridOccupancy; + +import java.util.ArrayList; + +/** + * Task to add auto-created workspace items. + */ +public class AddWorkspaceItemsTask extends ExtendedModelTask { + + private final ArrayList<? extends ItemInfo> mWorkspaceApps; + + /** + * @param workspaceApps items to add on the workspace + */ + public AddWorkspaceItemsTask(ArrayList<? extends ItemInfo> workspaceApps) { + mWorkspaceApps = workspaceApps; + } + + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + if (mWorkspaceApps.isEmpty()) { + return; + } + Context context = app.getContext(); + + final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>(); + final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>(); + + // 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<Long> workspaceScreens = LauncherModel.loadWorkspaceScreensDb(context); + synchronized(dataModel) { + for (ItemInfo item : mWorkspaceApps) { + if (item instanceof ShortcutInfo) { + // Short-circuit this logic if the icon exists somewhere on the workspace + if (shortcutExists(dataModel, item.getIntent(), item.user)) { + continue; + } + } + + // Find appropriate space for the item. + Pair<Long, int[]> coords = findSpaceForItem( + app, dataModel, workspaceScreens, addedWorkspaceScreensFinal, 1, 1); + long screenId = coords.first; + int[] cordinates = coords.second; + + ItemInfo itemInfo; + if (item instanceof ShortcutInfo || item instanceof FolderInfo) { + itemInfo = item; + } else if (item instanceof AppInfo) { + itemInfo = ((AppInfo) item).makeShortcut(); + } else { + throw new RuntimeException("Unexpected info type"); + } + + // Add the shortcut to the db + addItemToDatabase(context, itemInfo, screenId, cordinates); + + // Save the ShortcutInfo for binding in the workspace + addedShortcutsFinal.add(itemInfo); + } + } + + // Update the workspace screens + updateScreens(context, workspaceScreens); + + if (!addedShortcutsFinal.isEmpty()) { + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>(); + final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>(); + if (!addedShortcutsFinal.isEmpty()) { + ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1); + long lastScreenId = info.screenId; + for (ItemInfo i : addedShortcutsFinal) { + if (i.screenId == lastScreenId) { + addAnimated.add(i); + } else { + addNotAnimated.add(i); + } + } + } + callbacks.bindAppsAdded(addedWorkspaceScreensFinal, + addNotAnimated, addAnimated, null); + } + }); + } + } + + protected void addItemToDatabase(Context context, ItemInfo item, long screenId, int[] pos) { + LauncherModel.addItemToDatabase(context, item, + LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, pos[0], pos[1]); + } + + protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) { + LauncherModel.updateWorkspaceScreenOrder(context, workspaceScreens); + } + + /** + * Returns true if the shortcuts already exists on the workspace. This must be called after + * the workspace has been loaded. We identify a shortcut by its intent. + */ + protected boolean shortcutExists(BgDataModel dataModel, Intent intent, UserHandleCompat user) { + final String intentWithPkg, intentWithoutPkg; + if (intent.getComponent() != null) { + // If component is not null, an intent with null package will produce + // the same result and should also be a match. + String packageName = intent.getComponent().getPackageName(); + if (intent.getPackage() != null) { + intentWithPkg = intent.toUri(0); + intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0); + } else { + intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0); + intentWithoutPkg = intent.toUri(0); + } + } else { + intentWithPkg = intent.toUri(0); + intentWithoutPkg = intent.toUri(0); + } + + synchronized (dataModel) { + for (ItemInfo item : dataModel.itemsIdMap) { + if (item instanceof ShortcutInfo) { + ShortcutInfo info = (ShortcutInfo) item; + Intent targetIntent = info.promisedIntent == null + ? info.intent : info.promisedIntent; + if (targetIntent != null && info.user.equals(user)) { + Intent copyIntent = new Intent(targetIntent); + copyIntent.setSourceBounds(intent.getSourceBounds()); + String s = copyIntent.toUri(0); + if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) { + return true; + } + } + } + } + } + return false; + } + + /** + * Find a position on the screen for the given size or adds a new screen. + * @return screenId and the coordinates for the item. + */ + protected Pair<Long, int[]> findSpaceForItem( + LauncherAppState app, BgDataModel dataModel, + ArrayList<Long> workspaceScreens, + ArrayList<Long> addedWorkspaceScreensFinal, + int spanX, int spanY) { + LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>(); + + // Use sBgItemsIdMap as all the items are already loaded. + synchronized (dataModel) { + for (ItemInfo info : dataModel.itemsIdMap) { + if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + ArrayList<ItemInfo> items = screenItems.get(info.screenId); + if (items == null) { + items = new ArrayList<>(); + screenItems.put(info.screenId, items); + } + items.add(info); + } + } + } + + // Find appropriate space for the item. + long screenId = 0; + int[] cordinates = new int[2]; + boolean found = false; + + int screenCount = workspaceScreens.size(); + // First check the preferred screen. + int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1; + if (preferredScreenIndex < screenCount) { + screenId = workspaceScreens.get(preferredScreenIndex); + found = findNextAvailableIconSpaceInScreen( + app, screenItems.get(screenId), cordinates, spanX, spanY); + } + + if (!found) { + // Search on any of the screens starting from the first screen. + for (int screen = 1; screen < screenCount; screen++) { + screenId = workspaceScreens.get(screen); + if (findNextAvailableIconSpaceInScreen( + app, screenItems.get(screenId), cordinates, spanX, spanY)) { + // We found a space for it + found = true; + break; + } + } + } + + if (!found) { + // Still no position found. Add a new screen to the end. + screenId = LauncherSettings.Settings.call(app.getContext().getContentResolver(), + LauncherSettings.Settings.METHOD_NEW_SCREEN_ID) + .getLong(LauncherSettings.Settings.EXTRA_VALUE); + + // Save the screen id for binding in the workspace + workspaceScreens.add(screenId); + addedWorkspaceScreensFinal.add(screenId); + + // If we still can't find an empty space, then God help us all!!! + if (!findNextAvailableIconSpaceInScreen( + app, screenItems.get(screenId), cordinates, spanX, spanY)) { + throw new RuntimeException("Can't find space to add the item"); + } + } + return Pair.create(screenId, cordinates); + } + + private boolean findNextAvailableIconSpaceInScreen( + LauncherAppState app, ArrayList<ItemInfo> occupiedPos, + int[] xy, int spanX, int spanY) { + InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); + + GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows); + if (occupiedPos != null) { + for (ItemInfo r : occupiedPos) { + occupied.markCells(r, true); + } + } + return occupied.findVacantCell(xy, spanX, spanY); + } + +} diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java new file mode 100644 index 000000000..9f24e9035 --- /dev/null +++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java @@ -0,0 +1,95 @@ +/* + * 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.model; + +import android.content.ComponentName; + +import com.android.launcher3.AllAppsList; +import com.android.launcher3.AppInfo; +import com.android.launcher3.IconCache; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherModel.CallbackTask; +import com.android.launcher3.LauncherModel.Callbacks; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.compat.UserHandleCompat; + +import java.util.ArrayList; +import java.util.HashSet; + +/** + * Handles changes due to cache updates. + */ +public class CacheDataUpdatedTask extends ExtendedModelTask { + + public static final int OP_CACHE_UPDATE = 1; + public static final int OP_SESSION_UPDATE = 2; + + private final int mOp; + private final UserHandleCompat mUser; + private final HashSet<String> mPackages; + + public CacheDataUpdatedTask(int op, UserHandleCompat user, HashSet<String> packages) { + mOp = op; + mUser = user; + mPackages = packages; + } + + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + IconCache iconCache = app.getIconCache(); + + final ArrayList<AppInfo> updatedApps = new ArrayList<>(); + + ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>(); + synchronized (dataModel) { + for (ItemInfo info : dataModel.itemsIdMap) { + if (info instanceof ShortcutInfo && mUser.equals(info.user)) { + ShortcutInfo si = (ShortcutInfo) info; + ComponentName cn = si.getTargetComponent(); + if (isValidShortcut(si) && + cn != null && mPackages.contains(cn.getPackageName())) { + si.updateIcon(iconCache); + updatedShortcuts.add(si); + } + } + } + apps.updateIconsAndLabels(mPackages, mUser, updatedApps); + } + bindUpdatedShortcuts(updatedShortcuts, mUser); + + if (!updatedApps.isEmpty()) { + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + callbacks.bindAppsUpdated(updatedApps); + } + }); + } + } + + public boolean isValidShortcut(ShortcutInfo si) { + switch (mOp) { + case OP_CACHE_UPDATE: + return si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; + case OP_SESSION_UPDATE: + return si.isPromise(); + default: + return false; + } + } +} diff --git a/src/com/android/launcher3/model/ExtendedModelTask.java b/src/com/android/launcher3/model/ExtendedModelTask.java new file mode 100644 index 000000000..ccc600768 --- /dev/null +++ b/src/com/android/launcher3/model/ExtendedModelTask.java @@ -0,0 +1,61 @@ +/* + * 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.model; + +import com.android.launcher3.LauncherModel.CallbackTask; +import com.android.launcher3.LauncherModel.Callbacks; +import com.android.launcher3.LauncherModel.BaseModelUpdateTask; +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.MultiHashMap; + +import java.util.ArrayList; + +/** + * Extension of {@link BaseModelUpdateTask} with some utility methods + */ +public abstract class ExtendedModelTask extends BaseModelUpdateTask { + + public void bindUpdatedShortcuts( + ArrayList<ShortcutInfo> updatedShortcuts, UserHandleCompat user) { + bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user); + } + + public void bindUpdatedShortcuts( + final ArrayList<ShortcutInfo> updatedShortcuts, + final ArrayList<ShortcutInfo> removedShortcuts, + final UserHandleCompat user) { + if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) { + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + callbacks.bindShortcutsChanged(updatedShortcuts, removedShortcuts, user); + } + }); + } + } + + public void bindDeepShortcuts(BgDataModel dataModel) { + final MultiHashMap<ComponentKey, String> shortcutMapCopy = dataModel.deepShortcutMap.clone(); + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + callbacks.bindDeepShortcutMap(shortcutMapCopy); + } + }); + } +} diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java new file mode 100644 index 000000000..5d04325e8 --- /dev/null +++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java @@ -0,0 +1,86 @@ +/* + * 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.model; + +import android.content.ComponentName; + +import com.android.launcher3.AllAppsList; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherAppWidgetInfo; +import com.android.launcher3.LauncherModel.CallbackTask; +import com.android.launcher3.LauncherModel.Callbacks; +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.compat.PackageInstallerCompat; +import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; + +import java.util.HashSet; + +/** + * Handles changes due to a sessions updates for a currently installing app. + */ +public class PackageInstallStateChangedTask extends ExtendedModelTask { + + private final PackageInstallInfo mInstallInfo; + + public PackageInstallStateChangedTask(PackageInstallInfo installInfo) { + mInstallInfo = installInfo; + } + + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + if (mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLED) { + // Ignore install success events as they are handled by Package add events. + return; + } + + synchronized (dataModel) { + final HashSet<ItemInfo> updates = new HashSet<>(); + for (ItemInfo info : dataModel.itemsIdMap) { + if (info instanceof ShortcutInfo) { + ShortcutInfo si = (ShortcutInfo) info; + ComponentName cn = si.getTargetComponent(); + if (si.isPromise() && (cn != null) + && mInstallInfo.packageName.equals(cn.getPackageName())) { + si.setInstallProgress(mInstallInfo.progress); + + if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED) { + // Mark this info as broken. + si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE; + } + updates.add(si); + } + } + } + + for (LauncherAppWidgetInfo widget : dataModel.appWidgets) { + if (widget.providerName.getPackageName().equals(mInstallInfo.packageName)) { + widget.installProgress = mInstallInfo.progress; + updates.add(widget); + } + } + + if (!updates.isEmpty()) { + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + callbacks.bindRestoreItemsChange(updates); + } + }); + } + } + } +} diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java new file mode 100644 index 000000000..7286bf51f --- /dev/null +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -0,0 +1,378 @@ +/* + * 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.model; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.Bitmap; +import android.util.Log; + +import com.android.launcher3.AllAppsList; +import com.android.launcher3.AppInfo; +import com.android.launcher3.IconCache; +import com.android.launcher3.InstallShortcutReceiver; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherAppWidgetInfo; +import com.android.launcher3.LauncherModel; +import com.android.launcher3.LauncherModel.CallbackTask; +import com.android.launcher3.LauncherModel.Callbacks; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.Utilities; +import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.compat.UserManagerCompat; +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 java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; + +/** + * Handles updates due to changes in package manager (app installed/updated/removed) + * or when a user availability changes. + */ +public class PackageUpdatedTask extends ExtendedModelTask { + + private static final boolean DEBUG = false; + private static final String TAG = "PackageUpdatedTask"; + + public static final int OP_NONE = 0; + public static final int OP_ADD = 1; + public static final int OP_UPDATE = 2; + public static final int OP_REMOVE = 3; // uninstalled + public static final int OP_UNAVAILABLE = 4; // external media unmounted + public static final int OP_SUSPEND = 5; // package suspended + public static final int OP_UNSUSPEND = 6; // package unsuspended + public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable + + private final int mOp; + private final UserHandleCompat mUser; + private final String[] mPackages; + + public PackageUpdatedTask(int op, UserHandleCompat user, String... packages) { + mOp = op; + mUser = user; + mPackages = packages; + } + + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) { + final Context context = app.getContext(); + final IconCache iconCache = app.getIconCache(); + + final String[] packages = mPackages; + final int N = packages.length; + FlagOp flagOp = FlagOp.NO_OP; + final HashSet<String> packageSet = new HashSet<>(Arrays.asList(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); + appsList.addPackage(context, packages[i], mUser); + } + + ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser); + if (heuristic != null) { + heuristic.processPackageAdd(mPackages); + } + break; + } + case OP_UPDATE: + for (int i = 0; i < N; i++) { + if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]); + iconCache.updateIconsForPkg(packages[i], mUser); + appsList.updatePackage(context, packages[i], mUser); + app.getWidgetCache().removePackage(packages[i], mUser); + } + // Since package was just updated, the target must be available now. + 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); + } + // Fall through + } + case OP_UNAVAILABLE: + for (int i = 0; i < N; i++) { + if (DEBUG) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]); + appsList.removePackage(packages[i], mUser); + app.getWidgetCache().removePackage(packages[i], mUser); + } + flagOp = FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE); + break; + case OP_SUSPEND: + case OP_UNSUSPEND: + flagOp = mOp == OP_SUSPEND ? + FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED) : + FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED); + if (DEBUG) Log.d(TAG, "mAllAppsList.(un)suspend " + N); + appsList.updateDisabledFlags( + ItemInfoMatcher.ofPackages(packageSet, mUser), flagOp); + break; + case OP_USER_AVAILABILITY_CHANGE: + flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser) + ? FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER) + : FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER); + // We want to update all packages for this user. + appsList.updateDisabledFlags(ItemInfoMatcher.ofUser(mUser), flagOp); + break; + } + + ArrayList<AppInfo> added = null; + ArrayList<AppInfo> modified = null; + final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>(); + + if (appsList.added.size() > 0) { + added = new ArrayList<>(appsList.added); + appsList.added.clear(); + } + if (appsList.modified.size() > 0) { + modified = new ArrayList<>(appsList.modified); + appsList.modified.clear(); + } + if (appsList.removed.size() > 0) { + removedApps.addAll(appsList.removed); + appsList.removed.clear(); + } + + final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<>(); + + if (added != null) { + final ArrayList<AppInfo> addedApps = added; + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + callbacks.bindAppsAdded(null, null, null, addedApps); + } + }); + for (AppInfo ai : added) { + addedOrUpdatedApps.put(ai.componentName, ai); + } + } + + if (modified != null) { + final ArrayList<AppInfo> modifiedFinal = modified; + for (AppInfo ai : modified) { + addedOrUpdatedApps.put(ai.componentName, ai); + } + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + callbacks.bindAppsUpdated(modifiedFinal); + } + }); + } + + // Update shortcut infos + if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) { + final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>(); + final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<>(); + final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>(); + + synchronized (dataModel) { + for (ItemInfo info : dataModel.itemsIdMap) { + if (info instanceof ShortcutInfo && mUser.equals(info.user)) { + ShortcutInfo si = (ShortcutInfo) info; + boolean infoUpdated = false; + boolean shortcutUpdated = false; + + // Update shortcuts which use iconResource. + if ((si.iconResource != null) + && packageSet.contains(si.iconResource.packageName)) { + Bitmap icon = LauncherIcons.createIconBitmap( + si.iconResource.packageName, + si.iconResource.resourceName, context); + if (icon != null) { + si.setIcon(icon); + si.usingFallbackIcon = false; + infoUpdated = true; + } + } + + ComponentName cn = si.getTargetComponent(); + if (cn != null && packageSet.contains(cn.getPackageName())) { + AppInfo appInfo = addedOrUpdatedApps.get(cn); + + if (si.isPromise()) { + if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) { + // Auto install icon + PackageManager pm = context.getPackageManager(); + ResolveInfo matched = pm.resolveActivity( + new Intent(Intent.ACTION_MAIN) + .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER), + PackageManager.MATCH_DEFAULT_ONLY); + if (matched == null) { + // Try to find the best match activity. + Intent intent = pm.getLaunchIntentForPackage( + cn.getPackageName()); + if (intent != null) { + cn = intent.getComponent(); + appInfo = addedOrUpdatedApps.get(cn); + } + + if ((intent == null) || (appInfo == null)) { + removedShortcuts.add(si); + continue; + } + si.promisedIntent = intent; + } + } + + si.intent = si.promisedIntent; + si.promisedIntent = null; + si.status = ShortcutInfo.DEFAULT; + infoUpdated = true; + si.updateIcon(iconCache); + } + + if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction()) + && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { + si.updateIcon(iconCache); + si.title = Utilities.trim(appInfo.title); + si.contentDescription = appInfo.contentDescription; + infoUpdated = true; + } + + int oldDisabledFlags = si.isDisabled; + si.isDisabled = flagOp.apply(si.isDisabled); + if (si.isDisabled != oldDisabledFlags) { + shortcutUpdated = true; + } + } + + if (infoUpdated || shortcutUpdated) { + updatedShortcuts.add(si); + } + if (infoUpdated) { + LauncherModel.updateItemInDatabase(context, si); + } + } else if (info instanceof LauncherAppWidgetInfo && mOp == OP_ADD) { + LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info; + if (mUser.equals(widgetInfo.user) + && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) + && packageSet.contains(widgetInfo.providerName.getPackageName())) { + widgetInfo.restoreStatus &= + ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY & + ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; + + // adding this flag ensures that launcher shows 'click to setup' + // if the widget has a config activity. In case there is no config + // activity, it will be marked as 'restored' during bind. + widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY; + + widgets.add(widgetInfo); + LauncherModel.updateItemInDatabase(context, widgetInfo); + } + } + } + } + + bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser); + if (!removedShortcuts.isEmpty()) { + LauncherModel.deleteItemsFromDatabase(context, removedShortcuts); + } + + if (!widgets.isEmpty()) { + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + callbacks.bindWidgetsRestored(widgets); + } + }); + } + } + + final HashSet<String> removedPackages = new HashSet<>(); + final HashSet<ComponentName> removedComponents = new HashSet<>(); + if (mOp == OP_REMOVE) { + // Mark all packages in the broadcast to be removed + Collections.addAll(removedPackages, packages); + + // No need to update the removedComponents as + // removedPackages is a super-set of removedComponents + } else if (mOp == OP_UPDATE) { + // Mark disabled packages in the broadcast to be removed + final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); + for (int i=0; i<N; i++) { + if (!launcherApps.isPackageEnabledForProfile(packages[i], mUser)) { + removedPackages.add(packages[i]); + } + } + + // Update removedComponents as some components can get removed during package update + for (AppInfo info : removedApps) { + removedComponents.add(info.componentName); + } + } + + if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) { + LauncherModel.deleteItemsFromDatabase( + context, ItemInfoMatcher.ofPackages(removedPackages, mUser)); + LauncherModel.deleteItemsFromDatabase( + context, ItemInfoMatcher.ofComponents(removedComponents, mUser)); + + // Remove any queued items from the install queue + InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser); + + // Call the components-removed callback + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + callbacks.bindWorkspaceComponentsRemoved( + removedPackages, removedComponents, mUser); + } + }); + } + + if (!removedApps.isEmpty()) { + // Remove corresponding apps from All-Apps + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + callbacks.bindAppInfosRemoved(removedApps); + } + }); + } + + // Notify launcher of widget update. From marshmallow onwards we use AppWidgetHost to + // get widget update signals. + if (!Utilities.ATLEAST_MARSHMALLOW && + (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE)) { + scheduleCallbackTask(new CallbackTask() { + @Override + public void execute(Callbacks callbacks) { + callbacks.notifyWidgetProvidersChanged(); + } + }); + } + } +} diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java new file mode 100644 index 000000000..8f7c21db0 --- /dev/null +++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java @@ -0,0 +1,113 @@ +/* + * 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.model; + +import android.content.Context; + +import com.android.launcher3.AllAppsList; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherModel; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.shortcuts.DeepShortcutManager; +import com.android.launcher3.shortcuts.ShortcutInfoCompat; +import com.android.launcher3.util.MultiHashMap; + +import java.util.ArrayList; +import java.util.List; + +/** + * Handles changes due to shortcut manager updates (deep shortcut changes) + */ +public class ShortcutsChangedTask extends ExtendedModelTask { + + private final String mPackageName; + private final List<ShortcutInfoCompat> mShortcuts; + private final UserHandleCompat mUser; + private final boolean mUpdateIdMap; + + public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts, + UserHandleCompat user, boolean updateIdMap) { + mPackageName = packageName; + mShortcuts = shortcuts; + mUser = user; + mUpdateIdMap = updateIdMap; + } + + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + DeepShortcutManager deepShortcutManager = app.getShortcutManager(); + deepShortcutManager.onShortcutsChanged(mShortcuts); + + // Find ShortcutInfo's that have changed on the workspace. + final ArrayList<ShortcutInfo> removedShortcutInfos = new ArrayList<>(); + MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>(); + for (ItemInfo itemInfo : dataModel.itemsIdMap) { + if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { + ShortcutInfo si = (ShortcutInfo) itemInfo; + if (si.getPromisedIntent().getPackage().equals(mPackageName) + && si.user.equals(mUser)) { + idsToWorkspaceShortcutInfos.addToList(si.getDeepShortcutId(), si); + } + } + } + + final Context context = LauncherAppState.getInstance().getContext(); + final ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>(); + if (!idsToWorkspaceShortcutInfos.isEmpty()) { + // Update the workspace to reflect the changes to updated shortcuts residing on it. + List<ShortcutInfoCompat> shortcuts = deepShortcutManager.queryForFullDetails( + mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser); + for (ShortcutInfoCompat fullDetails : shortcuts) { + List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos + .remove(fullDetails.getId()); + if (!fullDetails.isPinned()) { + // The shortcut was previously pinned but is no longer, so remove it from + // the workspace and our pinned shortcut counts. + // Note that we put this check here, after querying for full details, + // because there's a possible race condition between pinning and + // receiving this callback. + removedShortcutInfos.addAll(shortcutInfos); + continue; + } + for (ShortcutInfo shortcutInfo : shortcutInfos) { + shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context); + updatedShortcutInfos.add(shortcutInfo); + } + } + } + + // If there are still entries in idsToWorkspaceShortcutInfos, that means that + // the corresponding shortcuts weren't passed in onShortcutsChanged(). This + // means they were cleared, so we remove and unpin them now. + for (String id : idsToWorkspaceShortcutInfos.keySet()) { + removedShortcutInfos.addAll(idsToWorkspaceShortcutInfos.get(id)); + } + + bindUpdatedShortcuts(updatedShortcutInfos, removedShortcutInfos, mUser); + if (!removedShortcutInfos.isEmpty()) { + LauncherModel.deleteItemsFromDatabase(context, removedShortcutInfos); + } + + if (mUpdateIdMap) { + // Update the deep shortcut map if the list of ids has changed for an activity. + dataModel.updateDeepShortcutMap(mPackageName, mUser, mShortcuts); + bindDeepShortcuts(dataModel); + } + } +} diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java new file mode 100644 index 000000000..b7b52a448 --- /dev/null +++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java @@ -0,0 +1,114 @@ +/* + * 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.model; + +import android.content.Context; + +import com.android.launcher3.AllAppsList; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherModel; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.shortcuts.DeepShortcutManager; +import com.android.launcher3.shortcuts.ShortcutInfoCompat; +import com.android.launcher3.shortcuts.ShortcutKey; +import com.android.launcher3.util.ComponentKey; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +/** + * Task to handle changing of lock state of the user + */ +public class UserLockStateChangedTask extends ExtendedModelTask { + + private final UserHandleCompat mUser; + + public UserLockStateChangedTask(UserHandleCompat user) { + mUser = user; + } + + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + Context context = app.getContext(); + boolean isUserUnlocked = UserManagerCompat.getInstance(context).isUserUnlocked(mUser); + DeepShortcutManager deepShortcutManager = app.getShortcutManager(); + + HashMap<ShortcutKey, ShortcutInfoCompat> pinnedShortcuts = new HashMap<>(); + if (isUserUnlocked) { + List<ShortcutInfoCompat> shortcuts = + deepShortcutManager.queryForPinnedShortcuts(null, mUser); + if (deepShortcutManager.wasLastCallSuccess()) { + for (ShortcutInfoCompat shortcut : shortcuts) { + pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut); + } + } else { + // Shortcut manager can fail due to some race condition when the lock state + // changes too frequently. For the purpose of the update, + // consider it as still locked. + isUserUnlocked = false; + } + } + + // Update the workspace to reflect the changes to updated shortcuts residing on it. + ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>(); + ArrayList<ShortcutInfo> deletedShortcutInfos = new ArrayList<>(); + for (ItemInfo itemInfo : dataModel.itemsIdMap) { + if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT + && mUser.equals(itemInfo.user)) { + ShortcutInfo si = (ShortcutInfo) itemInfo; + if (isUserUnlocked) { + ShortcutInfoCompat shortcut = + pinnedShortcuts.get(ShortcutKey.fromShortcutInfo(si)); + // We couldn't verify the shortcut during loader. If its no longer available + // (probably due to clear data), delete the workspace item as well + if (shortcut == null) { + deletedShortcutInfos.add(si); + continue; + } + si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_LOCKED_USER; + si.updateFromDeepShortcutInfo(shortcut, context); + } else { + si.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER; + } + updatedShortcutInfos.add(si); + } + } + bindUpdatedShortcuts(updatedShortcutInfos, deletedShortcutInfos, mUser); + if (!deletedShortcutInfos.isEmpty()) { + LauncherModel.deleteItemsFromDatabase(context, deletedShortcutInfos); + } + + // Remove shortcut id map for that user + Iterator<ComponentKey> keysIter = dataModel.deepShortcutMap.keySet().iterator(); + while (keysIter.hasNext()) { + if (keysIter.next().user.equals(mUser)) { + keysIter.remove(); + } + } + + if (isUserUnlocked) { + dataModel.updateDeepShortcutMap( + null, mUser, deepShortcutManager.queryForAllShortcuts(mUser)); + } + bindDeepShortcuts(dataModel); + } +} diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java index 6661429c1..78b7a3eee 100644 --- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java +++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java @@ -121,7 +121,7 @@ public class ManagedProfileHeuristic { // getting filled with the managed user apps, when it start with a fresh DB (or after // a very long time). if (userAppsExisted && !homescreenApps.isEmpty()) { - mModel.addAndBindAddedWorkspaceItems(mContext, homescreenApps); + mModel.addAndBindAddedWorkspaceItems(homescreenApps); } } @@ -175,7 +175,7 @@ public class ManagedProfileHeuristic { // Add the item to home screen and DB. This also generates an item id synchronously. ArrayList<ItemInfo> itemList = new ArrayList<ItemInfo>(1); itemList.add(workFolder); - mModel.addAndBindAddedWorkspaceItems(mContext, itemList); + mModel.addAndBindAddedWorkspaceItems(itemList); mPrefs.edit().putLong(folderIdKey, workFolder.id).apply(); saveWorkFolderShortcuts(workFolder.id, 0, workFolderApps); @@ -200,7 +200,6 @@ public class ManagedProfileHeuristic { } } - /** * Verifies that entries corresponding to {@param users} exist and removes all invalid entries. */ |