From bedfa5e3c344273d8c45a603ba3456acbac801e9 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 2 Oct 2014 15:58:31 -0700 Subject: Keeping icons in disabled state when SD-card is unmounted > changing shortcutInfo.isDisabled to be a flag based variable > on received OnPackageUnavailable, icons are disabled from desktop instead of being removed. Icons in all apps are removed Bug: 15852084 Bug: 16238283 Change-Id: I126d23c709682a917d4bbb84de71032593dce8f9 --- res/values/strings.xml | 2 + src/com/android/launcher3/AllAppsList.java | 6 +- src/com/android/launcher3/BubbleTextView.java | 2 +- src/com/android/launcher3/Launcher.java | 30 +++-- src/com/android/launcher3/LauncherModel.java | 123 +++++++++++++-------- src/com/android/launcher3/ShortcutInfo.java | 12 +- src/com/android/launcher3/Workspace.java | 37 ++++++- .../launcher3/compat/LauncherAppsCompat.java | 13 ++- .../launcher3/compat/LauncherAppsCompatV16.java | 17 ++- 9 files changed, 171 insertions(+), 71 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index adaa25a04..c5498b828 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -40,6 +40,8 @@ App isn\'t installed. + + App isn\'t available Downloaded app disabled in Safe mode diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java index 36d0ee78b..8b5e36205 100644 --- a/src/com/android/launcher3/AllAppsList.java +++ b/src/com/android/launcher3/AllAppsList.java @@ -105,7 +105,7 @@ class AllAppsList { /** * Remove the apps for the given apk identified by packageName. */ - public void removePackage(String packageName, UserHandleCompat user) { + public void removePackage(String packageName, UserHandleCompat user, boolean clearCache) { final List data = this.data; for (int i = data.size() - 1; i >= 0; i--) { AppInfo info = data.get(i); @@ -115,7 +115,9 @@ class AllAppsList { data.remove(i); } } - mIconCache.remove(packageName, user); + if (clearCache) { + mIconCache.remove(packageName, user); + } } /** diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index d0fb5c45a..d26577ac0 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -122,7 +122,7 @@ public class BubbleTextView extends TextView { LauncherAppState app = LauncherAppState.getInstance(); FastBitmapDrawable iconDrawable = Utilities.createIconDrawable(b); - iconDrawable.setGhostModeEnabled(info.isDisabled); + iconDrawable.setGhostModeEnabled(info.isDisabled != 0); setCompoundDrawables(null, iconDrawable, null, null); if (setDefaultPadding) { diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 382c24b61..20c923914 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -3089,6 +3089,16 @@ public class Launcher extends Activity // Open shortcut final ShortcutInfo shortcut = (ShortcutInfo) tag; + + if (shortcut.isDisabled != 0) { + int error = R.string.activity_not_available; + if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) { + error = R.string.safemode_shortcut_error; + } + Toast.makeText(this, error, Toast.LENGTH_SHORT).show(); + return; + } + final Intent intent = shortcut.intent; // Check for special shortcuts @@ -5649,24 +5659,30 @@ public class Launcher extends Activity * we only remove specific components from the workspace, where as * package-removal should clear all items by package name. * + * @param reason if non-zero, the icons are not permanently removed, rather marked as disabled. * Implementation of the method from LauncherModel.Callbacks. */ + @Override public void bindComponentsRemoved(final ArrayList packageNames, - final ArrayList appInfos, final UserHandleCompat user) { + final ArrayList appInfos, final UserHandleCompat user, final int reason) { Runnable r = new Runnable() { public void run() { - bindComponentsRemoved(packageNames, appInfos, user); + bindComponentsRemoved(packageNames, appInfos, user, reason); } }; if (waitUntilResume(r)) { return; } - if (!packageNames.isEmpty()) { - mWorkspace.removeItemsByPackageName(packageNames, user); - } - if (!appInfos.isEmpty()) { - mWorkspace.removeItemsByApplicationInfo(appInfos, user); + if (reason == 0) { + if (!packageNames.isEmpty()) { + mWorkspace.removeItemsByPackageName(packageNames, user); + } + if (!appInfos.isEmpty()) { + mWorkspace.removeItemsByApplicationInfo(appInfos, user); + } + } else { + mWorkspace.disableShortcutsByPackageName(packageNames, user, reason); } // Notify the drag controller diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 54b2008bb..2d44909dd 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -211,7 +211,7 @@ public class LauncherModel extends BroadcastReceiver public void updatePackageState(ArrayList installInfo); public void updatePackageBadge(String packageName); public void bindComponentsRemoved(ArrayList packageNames, - ArrayList appInfos, UserHandleCompat user); + ArrayList appInfos, UserHandleCompat user, int reason); public void bindPackagesUpdated(ArrayList widgetsAndShortcuts); public void bindSearchablesChanged(); public boolean isAllAppsButtonRank(int rank); @@ -2187,6 +2187,7 @@ public class LauncherModel extends BroadcastReceiver long serialNumber = c.getInt(profileIdIndex); user = mUserManager.getUserForSerialNumber(serialNumber); int promiseType = c.getInt(restoredIndex); + int disabledState = 0; if (user == null) { // User has been deleted remove the item. itemsToRemove.add(id); @@ -2260,14 +2261,13 @@ public class LauncherModel extends BroadcastReceiver itemsToRemove.add(id); continue; } - } else if (isSdCardReady) { - // Do not wait for external media load anymore. - // Log the invalid package, and remove it - Launcher.addDumpLog(TAG, - "Invalid package removed: " + cn, true); - itemsToRemove.add(id); - continue; - } else { + } else if (launcherApps.isAppEnabled( + manager, cn.getPackageName(), + PackageManager.GET_UNINSTALLED_PACKAGES)) { + // Package is present but not available. + allowMissingTarget = true; + disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE; + } else if (!isSdCardReady) { // SdCard is not ready yet. Package might get available, // once it is ready. Launcher.addDumpLog(TAG, "Invalid package: " + cn @@ -2280,6 +2280,14 @@ public class LauncherModel extends BroadcastReceiver pkgs.add(cn.getPackageName()); allowMissingTarget = true; // Add the icon on the workspace anyway. + + } else { + // Do not wait for external media load anymore. + // Log the invalid package, and remove it + Launcher.addDumpLog(TAG, + "Invalid package removed: " + cn, true); + itemsToRemove.add(id); + continue; } } else if (cn == null) { // For shortcuts with no component, keep them as they are @@ -2344,8 +2352,10 @@ public class LauncherModel extends BroadcastReceiver info.spanX = 1; info.spanY = 1; info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber); - info.isDisabled = isSafeMode - && !Utilities.isSystemApp(context, intent); + info.isDisabled = disabledState; + if (isSafeMode && !Utilities.isSystemApp(context, intent)) { + info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE; + } // check & update map of what's occupied deleteOnInvalidPlacement.set(false); @@ -3270,20 +3280,34 @@ public class LauncherModel extends BroadcastReceiver synchronized (sBgLock) { final LauncherAppsCompat launcherApps = LauncherAppsCompat .getInstance(mApp.getContext()); - ArrayList packagesRemoved; + final PackageManager manager = context.getPackageManager(); + final ArrayList packagesRemoved = new ArrayList(); + final ArrayList packagesUnavailable = new ArrayList(); for (Entry> entry : sPendingPackages.entrySet()) { UserHandleCompat user = entry.getKey(); - packagesRemoved = new ArrayList(); + packagesRemoved.clear(); + packagesUnavailable.clear(); for (String pkg : entry.getValue()) { if (!launcherApps.isPackageEnabledForProfile(pkg, user)) { - Launcher.addDumpLog(TAG, "Package not found: " + pkg, true); - packagesRemoved.add(pkg); + boolean packageOnSdcard = launcherApps.isAppEnabled( + manager, pkg, PackageManager.GET_UNINSTALLED_PACKAGES); + if (packageOnSdcard) { + Launcher.addDumpLog(TAG, "Package found on sd-card: " + pkg, true); + packagesUnavailable.add(pkg); + } else { + Launcher.addDumpLog(TAG, "Package not found: " + pkg, true); + packagesRemoved.add(pkg); + } } } if (!packagesRemoved.isEmpty()) { enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE, packagesRemoved.toArray(new String[packagesRemoved.size()]), user)); } + if (!packagesUnavailable.isEmpty()) { + enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE, + packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user)); + } } sPendingPackages.clear(); } @@ -3389,9 +3413,10 @@ public class LauncherModel extends BroadcastReceiver break; case OP_REMOVE: case OP_UNAVAILABLE: + boolean clearCache = mOp == OP_REMOVE; for (int i=0; i infos = getItemInfoForComponentName(a.componentName, mUser); for (ItemInfo i : infos) { - if (isShortcutInfoUpdateable(i)) { + if (i instanceof ShortcutInfo && isShortcutAppTarget((ShortcutInfo) i)) { ShortcutInfo info = (ShortcutInfo) i; info.title = a.title.toString(); info.contentDescription = a.contentDescription; @@ -3462,7 +3487,7 @@ public class LauncherModel extends BroadcastReceiver if (mOp == OP_ADD || mOp == OP_UPDATE) { final ArrayList iconsChanged = new ArrayList(); HashSet packageSet = new HashSet(Arrays.asList(packages)); - // We need to iteration over the items here, so that we can avoid new Bitmap + // We need to iterate over the items here, so that we can avoid new Bitmap // creation on the UI thread. synchronized (sBgLock) { for (ItemInfo info : sBgWorkspaceItems) { @@ -3497,28 +3522,35 @@ public class LauncherModel extends BroadcastReceiver final ArrayList removedPackageNames = new ArrayList(); - if (mOp == OP_REMOVE) { + if (mOp == OP_REMOVE || mOp == OP_UNAVAILABLE) { // Mark all packages in the broadcast to be removed removedPackageNames.addAll(Arrays.asList(packages)); } else if (mOp == OP_UPDATE) { // Mark disabled packages in the broadcast to be removed - final PackageManager pm = context.getPackageManager(); for (int i=0; i infos = getItemInfoForComponentName(a.componentName, mUser); - deleteItemsFromDatabase(context, infos); - } + if (!removedPackageNames.isEmpty() || !removedApps.isEmpty()) { + final int removeReason; + if (mOp == OP_UNAVAILABLE) { + removeReason = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE; + } else { + // Remove all the components associated with this package + for (String pn : removedPackageNames) { + deletePackageFromDatabase(context, pn, mUser); + } + // Remove all the specific components + for (AppInfo a : removedApps) { + ArrayList infos = getItemInfoForComponentName(a.componentName, mUser); + deleteItemsFromDatabase(context, infos); + } + removeReason = 0; + } + // Remove any queued items from the install queue String spKey = LauncherAppState.getSharedPreferencesKey(); SharedPreferences sp = @@ -3529,7 +3561,8 @@ public class LauncherModel extends BroadcastReceiver public void run() { Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; if (callbacks == cb && cb != null) { - callbacks.bindComponentsRemoved(removedPackageNames, removedApps, mUser); + callbacks.bindComponentsRemoved( + removedPackageNames, removedApps, mUser, removeReason); } } }); @@ -3779,24 +3812,18 @@ public class LauncherModel extends BroadcastReceiver return filterItemInfos(sBgItemsIdMap.values(), filter); } - public static boolean isShortcutInfoUpdateable(ItemInfo i) { - if (i instanceof ShortcutInfo) { - ShortcutInfo info = (ShortcutInfo) i; - // We need to check for ACTION_MAIN otherwise getComponent() might - // return null for some shortcuts (for instance, for shortcuts to - // web pages.) - Intent intent = info.intent; - ComponentName name = intent.getComponent(); - if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION && - Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { - return true; - } - // placeholder shortcuts get special treatment, let them through too. - if (info.isPromise()) { - return true; - } - } - return false; + /** + * @return true if the ShortcutInfo points to an app shortcut target, i.e. it has been added by + * dragging from AllApps list. + */ + public static boolean isShortcutAppTarget(ShortcutInfo info) { + // We need to check for ACTION_MAIN otherwise getComponent() might + // return null for some shortcuts (for instance, for shortcuts to + // web pages.) + Intent intent = info.promisedIntent != null ? info.promisedIntent : info.intent; + ComponentName name = intent.getComponent(); + return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION && + Intent.ACTION_MAIN.equals(intent.getAction()) && name != null; } /** diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index daf343460..01f79314e 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -87,11 +87,21 @@ public class ShortcutInfo extends ItemInfo { */ private Bitmap mIcon; + /** + * Indicates that the icon is disabled due to safe mode restrictions. + */ + public static final int FLAG_DISABLED_SAFEMODE = 1; + + /** + * Indicates that the icon is disabled as the app is not available. + */ + public static final int FLAG_DISABLED_NOT_AVAILABLE = 2; + /** * Could be disabled, if the the app is installed but unavailable (eg. in safe mode or when * sd-card is not available). */ - boolean isDisabled = false; + int isDisabled = DEFAULT; int status; diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index e34edf32a..db997db2b 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -4831,6 +4831,34 @@ public class Workspace extends SmoothPagedView }); } + public void disableShortcutsByPackageName(final ArrayList packages, + final UserHandleCompat user, final int reason) { + final HashSet packageNames = new HashSet(); + packageNames.addAll(packages); + + mapOverItems(MAP_RECURSE, new ItemOperator() { + @Override + public boolean evaluate(ItemInfo info, View v, View parent) { + if (info instanceof ShortcutInfo && v instanceof BubbleTextView) { + ShortcutInfo shortcutInfo = (ShortcutInfo) info; + ComponentName cn = shortcutInfo.getTargetComponent(); + if (user.equals(shortcutInfo.user) && cn != null + && packageNames.contains(cn.getPackageName())) { + shortcutInfo.isDisabled |= reason; + BubbleTextView shortcut = (BubbleTextView) v; + shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache, true, false); + + if (parent != null) { + parent.invalidate(); + } + } + } + // process all the shortcuts + return false; + } + }); + } + // Removes ALL items that match a given package name, this is usually called when a package // has been removed and we want to remove all components (widgets, shortcuts, apps) that // belong to that package. @@ -5061,7 +5089,6 @@ public class Workspace extends SmoothPagedView ComponentName cn = shortcutInfo.getTargetComponent(); AppInfo appInfo = appsMap.get(cn); if (user.equals(shortcutInfo.user) && cn != null - && LauncherModel.isShortcutInfoUpdateable(info) && pkgNames.contains(cn.getPackageName())) { boolean promiseStateChanged = false; boolean infoUpdated = false; @@ -5106,8 +5133,14 @@ public class Workspace extends SmoothPagedView LauncherModel.updateItemInDatabase(getContext(), shortcutInfo); } + if ((shortcutInfo.isDisabled & ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE) != 0) { + // Since package was just updated, the target must be available now. + shortcutInfo.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE; + infoUpdated = true; + } - if (appInfo != null) { + // Only update the icon and labels if the shortcuts points to an app target + if ((appInfo != null) && LauncherModel.isShortcutAppTarget(shortcutInfo)) { shortcutInfo.updateIcon(mIconCache); shortcutInfo.title = appInfo.title.toString(); shortcutInfo.contentDescription = appInfo.contentDescription; diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java index 6efcc00fd..5858bc8b9 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompat.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java @@ -19,8 +19,10 @@ package com.android.launcher3.compat; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Rect; -import android.os.Build; import android.os.Bundle; import com.android.launcher3.Utilities; @@ -73,4 +75,13 @@ public abstract class LauncherAppsCompat { public abstract boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user); public abstract boolean isActivityEnabledForProfile(ComponentName component, UserHandleCompat user); + + public boolean isAppEnabled(PackageManager pm, String packageName, int flags) { + try { + ApplicationInfo info = pm.getApplicationInfo(packageName, flags); + return info != null && info.enabled; + } catch (NameNotFoundException e) { + return false; + } + } } \ No newline at end of file diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java index 7e5e6bf2c..e47b9a58d 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java @@ -22,7 +22,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; -import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; @@ -112,12 +111,7 @@ public class LauncherAppsCompatV16 extends LauncherAppsCompat { } public boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user) { - try { - PackageInfo info = mPm.getPackageInfo(packageName, 0); - return info != null && info.applicationInfo.enabled; - } catch (NameNotFoundException e) { - return false; - } + return isAppEnabled(mPm, packageName, 0); } public boolean isActivityEnabledForProfile(ComponentName component, UserHandleCompat user) { @@ -198,8 +192,13 @@ public class LauncherAppsCompatV16 extends LauncherAppsCompat { callback.onPackagesAvailable(packages, user, replacing); } } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { - final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, - Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT); + // This intent is broadcasted when moving a package or mounting/un-mounting + // external storage. + // However on Kitkat this is also sent when a package is being updated, and + // contains an extra Intent.EXTRA_REPLACING=true for that case. + // Using false as default for Intent.EXTRA_REPLACING gives correct value on + // lower devices as the intent is not sent when the app is updating/replacing. + final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); for (OnAppsChangedCallbackCompat callback : getCallbacks()) { callback.onPackagesUnavailable(packages, user, replacing); -- cgit v1.2.3