summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSunny Goyal <sunnygoyal@google.com>2016-11-10 00:50:22 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2016-11-10 00:50:22 +0000
commit1ef74f5151e24d6b0cfcd3faf2c4cc2bdf7ae35c (patch)
treea703cf47e54306c47bd846112bf761ce133d48c2
parent4ece6ed261b13f535618845552926bfd65a67be6 (diff)
parentf0ba8b7ca1dc8fd53451d3d16e9f4fc306cddcd4 (diff)
downloadandroid_packages_apps_Trebuchet-1ef74f5151e24d6b0cfcd3faf2c4cc2bdf7ae35c.tar.gz
android_packages_apps_Trebuchet-1ef74f5151e24d6b0cfcd3faf2c4cc2bdf7ae35c.tar.bz2
android_packages_apps_Trebuchet-1ef74f5151e24d6b0cfcd3faf2c4cc2bdf7ae35c.zip
Merge "Moving various runnables in LauncherModel to individual tasks" into ub-launcher3-master
-rw-r--r--build.gradle4
-rw-r--r--src/com/android/launcher3/AllAppsList.java15
-rw-r--r--src/com/android/launcher3/IconCache.java4
-rw-r--r--src/com/android/launcher3/InstallShortcutReceiver.java2
-rw-r--r--src/com/android/launcher3/LauncherAppWidgetInfo.java11
-rw-r--r--src/com/android/launcher3/LauncherModel.java968
-rw-r--r--src/com/android/launcher3/ShortcutInfo.java8
-rw-r--r--src/com/android/launcher3/model/AddWorkspaceItemsTask.java262
-rw-r--r--src/com/android/launcher3/model/CacheDataUpdatedTask.java95
-rw-r--r--src/com/android/launcher3/model/ExtendedModelTask.java61
-rw-r--r--src/com/android/launcher3/model/PackageInstallStateChangedTask.java86
-rw-r--r--src/com/android/launcher3/model/PackageUpdatedTask.java378
-rw-r--r--src/com/android/launcher3/model/ShortcutsChangedTask.java113
-rw-r--r--src/com/android/launcher3/model/UserLockStateChangedTask.java114
-rw-r--r--src/com/android/launcher3/util/ManagedProfileHeuristic.java5
-rw-r--r--tests/Android.mk3
-rw-r--r--tests/res/raw/cache_data_updated_task_data.txt28
-rw-r--r--tests/res/raw/package_install_state_change_task_data.txt24
-rw-r--r--tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java190
-rw-r--r--tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java208
-rw-r--r--tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java81
-rw-r--r--tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java61
22 files changed, 1812 insertions, 909 deletions
diff --git a/build.gradle b/build.gradle
index 66cba5925..4629caae3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -45,6 +45,7 @@ android {
androidTest {
java.srcDirs = ['tests/src']
+ res.srcDirs = ['tests/res']
manifest.srcFile "tests/AndroidManifest.xml"
}
@@ -65,6 +66,9 @@ dependencies {
compile 'com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-2'
testCompile 'junit:junit:4.12'
+ androidTestCompile "org.mockito:mockito-core:1.+"
+ androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
+ androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
androidTestCompile 'com.android.support:support-annotations:23.2.0'
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.
*/
diff --git a/tests/Android.mk b/tests/Android.mk
index 61ee220e8..5103ced7c 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -17,11 +17,12 @@ LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator mockito-target-minus-junit4
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 21
LOCAL_PACKAGE_NAME := Launcher3Tests
diff --git a/tests/res/raw/cache_data_updated_task_data.txt b/tests/res/raw/cache_data_updated_task_data.txt
new file mode 100644
index 000000000..9095476f6
--- /dev/null
+++ b/tests/res/raw/cache_data_updated_task_data.txt
@@ -0,0 +1,28 @@
+# Model data used by CacheDataUpdatedTaskTest
+
+classMap s com.android.launcher3.ShortcutInfo
+
+# Items for the BgDataModel
+
+# App shortcuts
+bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
+bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
+bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
+bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
+
+# Auto install app shortcut
+bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
+bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
+
+# Custom shortcuts
+bgItem s itemType=1 title=app1-shrt intent=component=app1/class3 id=7
+bgItem s itemType=1 title=app4-shrt intent=component=app4/class1 id=8
+
+# Restored custom shortcut
+bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=9
+bgItem s itemType=1 status=1 title=app5-shrt intent=component=app5/class1 id=10
+
+allApps componentName=app1/class1
+allApps componentName=app1/class2
+allApps componentName=app2/class1
+allApps componentName=app2/class2 \ No newline at end of file
diff --git a/tests/res/raw/package_install_state_change_task_data.txt b/tests/res/raw/package_install_state_change_task_data.txt
new file mode 100644
index 000000000..84f9c161e
--- /dev/null
+++ b/tests/res/raw/package_install_state_change_task_data.txt
@@ -0,0 +1,24 @@
+# Model data used by PackageInstallStateChangeTaskTest
+
+classMap s com.android.launcher3.ShortcutInfo
+classMap w com.android.launcher3.LauncherAppWidgetInfo
+
+# Items for the BgDataModel
+
+# App shortcuts
+bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
+bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
+bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
+bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
+
+# Promise icons for app3
+bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
+bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
+bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=7
+
+# Promise icon for app4
+bgItem s itemType=1 status=1 title=app4-shrt intent=component=app4/class1 id=8
+
+# Widget
+bgItem w providerName=app4/provider1 id=9
+bgItem w providerName=app5/provider1 id=10 \ No newline at end of file
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
new file mode 100644
index 000000000..ecb3782fc
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
@@ -0,0 +1,190 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.util.Pair;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.LongArrayMap;
+
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link AddWorkspaceItemsTask}
+ */
+public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
+
+ private final ComponentName mComponent1 = new ComponentName("a", "b");
+ private final ComponentName mComponent2 = new ComponentName("b", "b");
+
+ private ArrayList<Long> existingScreens;
+ private ArrayList<Long> newScreens;
+ private LongArrayMap<GridOccupancy> screenOccupancy;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ existingScreens = new ArrayList<>();
+ screenOccupancy = new LongArrayMap<>();
+ newScreens = new ArrayList<>();
+
+ idp.numColumns = 5;
+ idp.numRows = 5;
+ }
+
+ private <T extends ItemInfo> AddWorkspaceItemsTask newTask(T... items) {
+ return new AddWorkspaceItemsTask(new ArrayList<>(Arrays.asList(items))) {
+
+ @Override
+ protected void addItemToDatabase(Context context, ItemInfo item,
+ long screenId, int[] pos) {
+ item.screenId = screenId;
+ item.cellX = pos[0];
+ item.cellY = pos[1];
+ }
+
+ @Override
+ protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) { }
+ };
+ }
+
+ public void testFindSpaceForItem_prefers_second() {
+ // First screen has only one hole of size 1
+ int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+
+ // Second screen has 2 holes of sizes 3x2 and 2x3
+ setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
+
+ Pair<Long, int[]> spaceFound = newTask()
+ .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 1, 1);
+ assertEquals(2L, (long) spaceFound.first);
+ assertTrue(screenOccupancy.get(spaceFound.first)
+ .isRegionVacant(spaceFound.second[0], spaceFound.second[1], 1, 1));
+
+ // Find a larger space
+ spaceFound = newTask()
+ .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 2, 3);
+ assertEquals(2L, (long) spaceFound.first);
+ assertTrue(screenOccupancy.get(spaceFound.first)
+ .isRegionVacant(spaceFound.second[0], spaceFound.second[1], 2, 3));
+ }
+
+ public void testFindSpaceForItem_adds_new_screen() throws Exception {
+ // First screen has 2 holes of sizes 3x2 and 2x3
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
+ commitScreensToDb();
+
+ when(appState.getContext()).thenReturn(getMockContext());
+
+ ArrayList<Long> oldScreens = new ArrayList<>(existingScreens);
+ Pair<Long, int[]> spaceFound = newTask()
+ .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 3, 3);
+ assertFalse(oldScreens.contains(spaceFound.first));
+ assertTrue(newScreens.contains(spaceFound.first));
+ }
+
+ public void testAddItem_existing_item_ignored() throws Exception {
+ ShortcutInfo info = new ShortcutInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+
+ // Setup a screen with a hole
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+ commitScreensToDb();
+
+ when(appState.getContext()).thenReturn(getMockContext());
+
+ // Nothing was added
+ assertTrue(executeTaskForTest(newTask(info)).isEmpty());
+ }
+
+ public void testAddItem_some_items_added() throws Exception {
+ ShortcutInfo info = new ShortcutInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+
+ ShortcutInfo info2 = new ShortcutInfo();
+ info2.intent = new Intent().setComponent(mComponent2);
+
+ // Setup a screen with a hole
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+ commitScreensToDb();
+
+ when(appState.getContext()).thenReturn(getMockContext());
+
+ executeTaskForTest(newTask(info, info2)).get(0).run();
+ ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
+ ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class);
+
+ // only info2 should be added because info was already added to the workspace
+ // in setupWorkspaceWithHoles()
+ verify(callbacks).bindAppsAdded(any(ArrayList.class), notAnimated.capture(),
+ animated.capture(), any(ArrayList.class));
+ assertTrue(notAnimated.getValue().isEmpty());
+
+ assertEquals(1, animated.getValue().size());
+ assertTrue(animated.getValue().contains(info2));
+ }
+
+ private int setupWorkspaceWithHoles(int startId, long screenId, Rect... holes) {
+ GridOccupancy occupancy = new GridOccupancy(idp.numColumns, idp.numRows);
+ occupancy.markCells(0, 0, idp.numColumns, idp.numRows, true);
+ for (Rect r : holes) {
+ occupancy.markCells(r, false);
+ }
+
+ existingScreens.add(screenId);
+ screenOccupancy.append(screenId, occupancy);
+
+ for (int x = 0; x < idp.numColumns; x++) {
+ for (int y = 0; y < idp.numRows; y++) {
+ if (!occupancy.cells[x][y]) {
+ continue;
+ }
+
+ ShortcutInfo info = new ShortcutInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+ info.id = startId++;
+ info.screenId = screenId;
+ info.cellX = x;
+ info.cellY = y;
+ info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ bgDataModel.addItem(info, false);
+ }
+ }
+ return startId;
+ }
+
+ private void commitScreensToDb() throws Exception {
+ LauncherSettings.Settings.call(getMockContentResolver(),
+ LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+
+ Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
+ ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+ // Clear the table
+ ops.add(ContentProviderOperation.newDelete(uri).build());
+ int count = existingScreens.size();
+ for (int i = 0; i < count; i++) {
+ ContentValues v = new ContentValues();
+ long screenId = existingScreens.get(i);
+ v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
+ v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
+ ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
+ }
+ getMockContentResolver().applyBatch(ProviderConfig.AUTHORITY, ops);
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
new file mode 100644
index 000000000..5628e8291
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -0,0 +1,208 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.support.test.InstrumentationRegistry;
+import android.test.ProviderTestCase2;
+
+import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.DeferredHandler;
+import com.android.launcher3.IconCache;
+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.Callbacks;
+import com.android.launcher3.LauncherModel.BaseModelUpdateTask;
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.TestLauncherProvider;
+
+import org.mockito.ArgumentCaptor;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.List;
+
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Base class for writing tests for Model update tasks.
+ */
+public class BaseModelUpdateTaskTestCase extends ProviderTestCase2<TestLauncherProvider> {
+
+ public final HashMap<Class, HashMap<String, Field>> fieldCache = new HashMap<>();
+
+ public Context targetContext;
+ public UserHandleCompat myUser;
+
+ public InvariantDeviceProfile idp;
+ public LauncherAppState appState;
+ public MyIconCache iconCache;
+
+ public BgDataModel bgDataModel;
+ public AllAppsList allAppsList;
+ public Callbacks callbacks;
+
+ public BaseModelUpdateTaskTestCase() {
+ super(TestLauncherProvider.class, ProviderConfig.AUTHORITY);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ callbacks = mock(Callbacks.class);
+ appState = mock(LauncherAppState.class);
+ myUser = UserHandleCompat.myUserHandle();
+
+ bgDataModel = new BgDataModel();
+ targetContext = InstrumentationRegistry.getTargetContext();
+ idp = new InvariantDeviceProfile();
+ iconCache = new MyIconCache(targetContext, idp);
+
+ allAppsList = new AllAppsList(iconCache, null);
+
+ when(appState.getIconCache()).thenReturn(iconCache);
+ when(appState.getInvariantDeviceProfile()).thenReturn(idp);
+ }
+
+ /**
+ * Synchronously executes the task and returns all the UI callbacks posted.
+ */
+ public List<Runnable> executeTaskForTest(BaseModelUpdateTask task) throws Exception {
+ LauncherModel mockModel = mock(LauncherModel.class);
+ when(mockModel.getCallback()).thenReturn(callbacks);
+
+ Field f = BaseModelUpdateTask.class.getDeclaredField("mModel");
+ f.setAccessible(true);
+ f.set(task, mockModel);
+
+ DeferredHandler mockHandler = mock(DeferredHandler.class);
+ f = BaseModelUpdateTask.class.getDeclaredField("mUiHandler");
+ f.setAccessible(true);
+ f.set(task, mockHandler);
+
+ task.execute(appState, bgDataModel, allAppsList);
+ ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mockHandler, atLeast(0)).post(captor.capture());
+
+ return captor.getAllValues();
+ }
+
+ /**
+ * Initializes mock data for the test.
+ */
+ public void initializeData(String resourceName) throws Exception {
+ Context myContext = InstrumentationRegistry.getContext();
+ Resources res = myContext.getResources();
+ int id = res.getIdentifier(resourceName, "raw", myContext.getPackageName());
+ try (BufferedReader reader =
+ new BufferedReader(new InputStreamReader(res.openRawResource(id)))) {
+ String line;
+ HashMap<String, Class> classMap = new HashMap<>();
+ while((line = reader.readLine()) != null) {
+ line = line.trim();
+ if (line.startsWith("#") || line.isEmpty()) {
+ continue;
+ }
+ String[] commands = line.split(" ");
+ switch (commands[0]) {
+ case "classMap":
+ classMap.put(commands[1], Class.forName(commands[2]));
+ break;
+ case "bgItem":
+ bgDataModel.addItem(
+ (ItemInfo) initItem(classMap.get(commands[1]), commands, 2), false);
+ break;
+ case "allApps":
+ allAppsList.add((AppInfo) initItem(AppInfo.class, commands, 1));
+ break;
+ }
+ }
+ }
+ }
+
+ private Object initItem(Class clazz, String[] fieldDef, int startIndex) throws Exception {
+ HashMap<String, Field> cache = fieldCache.get(clazz);
+ if (cache == null) {
+ cache = new HashMap<>();
+ Class c = clazz;
+ while (c != null) {
+ for (Field f : c.getDeclaredFields()) {
+ f.setAccessible(true);
+ cache.put(f.getName(), f);
+ }
+ c = c.getSuperclass();
+ }
+ fieldCache.put(clazz, cache);
+ }
+
+ Object item = clazz.newInstance();
+ for (int i = startIndex; i < fieldDef.length; i++) {
+ String[] fieldData = fieldDef[i].split("=", 2);
+ Field f = cache.get(fieldData[0]);
+ Class type = f.getType();
+ if (type == int.class || type == long.class) {
+ f.set(item, Integer.parseInt(fieldData[1]));
+ } else if (type == CharSequence.class || type == String.class) {
+ f.set(item, fieldData[1]);
+ } else if (type == Intent.class) {
+ if (!fieldData[1].startsWith("#Intent")) {
+ fieldData[1] = "#Intent;" + fieldData[1] + ";end";
+ }
+ f.set(item, Intent.parseUri(fieldData[1], 0));
+ } else if (type == ComponentName.class) {
+ f.set(item, ComponentName.unflattenFromString(fieldData[1]));
+ } else {
+ throw new Exception("Added parsing logic for "
+ + f.getName() + " of type " + f.getType());
+ }
+ }
+ return item;
+ }
+
+ public static class MyIconCache extends IconCache {
+
+ private final HashMap<ComponentKey, CacheEntry> mCache = new HashMap<>();
+
+ public MyIconCache(Context context, InvariantDeviceProfile idp) {
+ super(context, idp);
+ }
+
+ @Override
+ protected CacheEntry cacheLocked(ComponentName componentName,
+ LauncherActivityInfoCompat info, UserHandleCompat user,
+ boolean usePackageIcon, boolean useLowResIcon) {
+ CacheEntry entry = mCache.get(new ComponentKey(componentName, user));
+ if (entry == null) {
+ entry = new CacheEntry();
+ entry.icon = getDefaultIcon(user);
+ }
+ return entry;
+ }
+
+ public void addCache(ComponentName key, String title) {
+ CacheEntry entry = new CacheEntry();
+ entry.icon = newIcon();
+ entry.title = title;
+ mCache.put(new ComponentKey(key, UserHandleCompat.myUserHandle()), entry);
+ }
+
+ public Bitmap newIcon() {
+ return Bitmap.createBitmap(1, 1, Config.ARGB_8888);
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
new file mode 100644
index 000000000..25b8df933
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -0,0 +1,81 @@
+package com.android.launcher3.model;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.IconCache;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ShortcutInfo;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link CacheDataUpdatedTask}
+ */
+public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase {
+
+ private static final String NEW_LABEL_PREFIX = "new-label-";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ initializeData("cache_data_updated_task_data");
+ // Add dummy entries in the cache to simulate update
+ for (ItemInfo info : bgDataModel.itemsIdMap) {
+ iconCache.addCache(info.getTargetComponent(), NEW_LABEL_PREFIX + info.id);
+ }
+ }
+
+ private CacheDataUpdatedTask newTask(int op, String... pkg) {
+ return new CacheDataUpdatedTask(op, myUser, new HashSet<>(Arrays.asList(pkg)));
+ }
+
+ public void testCacheUpdate_update_apps() throws Exception {
+ executeTaskForTest(newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, "app1"));
+
+ // Verify that only the app icons of app1 (id 1 & 2) are updated. Custom shortcut (id 7)
+ // is not updated
+ verifyUpdate(1L, 2L);
+
+ // Verify that only app1 var updated in allAppsList
+ assertFalse(allAppsList.data.isEmpty());
+ for (AppInfo info : allAppsList.data) {
+ if (info.componentName.getPackageName().equals("app1")) {
+ assertNotNull(info.iconBitmap);
+ } else {
+ assertNull(info.iconBitmap);
+ }
+ }
+ }
+
+ public void testSessionUpdate_ignores_normal_apps() throws Exception {
+ executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1"));
+
+ // app1 has no restored shortcuts. Verify that nothing was updated.
+ verifyUpdate();
+ }
+
+ public void testSessionUpdate_updates_pending_apps() throws Exception {
+ executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app3"));
+
+ // app3 has only restored apps (id 5, 6) and shortcuts (id 9). Verify that only apps were
+ // were updated
+ verifyUpdate(5L, 6L);
+ }
+
+ private void verifyUpdate(Long... idsUpdated) {
+ HashSet<Long> updates = new HashSet<>(Arrays.asList(idsUpdated));
+ IconCache noOpIconCache = mock(IconCache.class);
+ for (ItemInfo info : bgDataModel.itemsIdMap) {
+ if (updates.contains(info.id)) {
+ assertEquals(NEW_LABEL_PREFIX + info.id, info.title);
+ assertNotNull(((ShortcutInfo) info).getIcon(noOpIconCache));
+ } else {
+ assertNotSame(NEW_LABEL_PREFIX + info.id, info.title);
+ assertNull(((ShortcutInfo) info).getIcon(noOpIconCache));
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
new file mode 100644
index 000000000..d6555620c
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -0,0 +1,61 @@
+package com.android.launcher3.model;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ * Tests for {@link PackageInstallStateChangedTask}
+ */
+public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ initializeData("package_install_state_change_task_data");
+ }
+
+ private PackageInstallStateChangedTask newTask(String pkg, int progress) {
+ PackageInstallInfo installInfo = new PackageInstallInfo(pkg);
+ installInfo.progress = progress;
+ installInfo.state = PackageInstallerCompat.STATUS_INSTALLING;
+ return new PackageInstallStateChangedTask(installInfo);
+ }
+
+ public void testSessionUpdate_ignore_installed() throws Exception {
+ executeTaskForTest(newTask("app1", 30));
+
+ // No shortcuts were updated
+ verifyProgressUpdate(0);
+ }
+
+ public void testSessionUpdate_shortcuts_updated() throws Exception {
+ executeTaskForTest(newTask("app3", 30));
+
+ verifyProgressUpdate(30, 5L, 6L, 7L);
+ }
+
+ public void testSessionUpdate_widgets_updated() throws Exception {
+ executeTaskForTest(newTask("app4", 30));
+
+ verifyProgressUpdate(30, 8L, 9L);
+ }
+
+ private void verifyProgressUpdate(int progress, Long... idsUpdated) {
+ HashSet<Long> updates = new HashSet<>(Arrays.asList(idsUpdated));
+ for (ItemInfo info : bgDataModel.itemsIdMap) {
+ if (info instanceof ShortcutInfo) {
+ assertEquals(updates.contains(info.id) ? progress: 0,
+ ((ShortcutInfo) info).getInstallProgress());
+ } else {
+ assertEquals(updates.contains(info.id) ? progress: -1,
+ ((LauncherAppWidgetInfo) info).installProgress);
+ }
+ }
+ }
+}