diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2016-09-09 15:47:55 -0700 |
---|---|---|
committer | Sunny Goyal <sunnygoyal@google.com> | 2016-11-09 15:50:08 -0800 |
commit | f0ba8b7ca1dc8fd53451d3d16e9f4fc306cddcd4 (patch) | |
tree | 821627bf354c858215e48c58cc5122a94ebd9c08 /src/com/android/launcher3/model | |
parent | 0d547bfd570e2e5d6ced55e24305d070fe2b78e2 (diff) | |
download | android_packages_apps_Trebuchet-f0ba8b7ca1dc8fd53451d3d16e9f4fc306cddcd4.tar.gz android_packages_apps_Trebuchet-f0ba8b7ca1dc8fd53451d3d16e9f4fc306cddcd4.tar.bz2 android_packages_apps_Trebuchet-f0ba8b7ca1dc8fd53451d3d16e9f4fc306cddcd4.zip |
Moving various runnables in LauncherModel to individual tasks
> Adding tests for some of the runnable
Change-Id: I1a315d38878857df3371f0e69d622a41fc3b081a
Diffstat (limited to 'src/com/android/launcher3/model')
7 files changed, 1109 insertions, 0 deletions
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); + } +} |