summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSunny Goyal <sunnygoyal@google.com>2014-10-02 22:58:31 (GMT)
committerDanesh M <daneshm90@gmail.com>2015-09-27 22:44:28 (GMT)
commitbedfa5e3c344273d8c45a603ba3456acbac801e9 (patch)
tree40caee9294fc229a80aeaef821381713e172f213
parent6d5f97506bc6b93471d7e29bd2cbd996895cc0d7 (diff)
downloadandroid_packages_apps_Trebuchet-bedfa5e3c344273d8c45a603ba3456acbac801e9.zip
android_packages_apps_Trebuchet-bedfa5e3c344273d8c45a603ba3456acbac801e9.tar.gz
android_packages_apps_Trebuchet-bedfa5e3c344273d8c45a603ba3456acbac801e9.tar.bz2
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
-rw-r--r--res/values/strings.xml2
-rw-r--r--src/com/android/launcher3/AllAppsList.java6
-rw-r--r--src/com/android/launcher3/BubbleTextView.java2
-rw-r--r--src/com/android/launcher3/Launcher.java30
-rw-r--r--src/com/android/launcher3/LauncherModel.java123
-rw-r--r--src/com/android/launcher3/ShortcutInfo.java12
-rw-r--r--src/com/android/launcher3/Workspace.java37
-rw-r--r--src/com/android/launcher3/compat/LauncherAppsCompat.java13
-rw-r--r--src/com/android/launcher3/compat/LauncherAppsCompatV16.java17
9 files changed, 171 insertions, 71 deletions
diff --git a/res/values/strings.xml b/res/values/strings.xml
index adaa25a..c5498b8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -40,6 +40,8 @@
<string name="folder_name"></string>
<!-- Displayed when user selects a shortcut for an app that was uninstalled [CHAR_LIMIT=none]-->
<string name="activity_not_found">App isn\'t installed.</string>
+ <!-- Displayed when user selects a shortcut for an app that is current not available [CHAR_LIMIT=none]-->
+ <string name="activity_not_available">App isn\'t available</string>
<!-- SafeMode shortcut error string -->
<string name="safemode_shortcut_error">Downloaded app disabled in Safe mode</string>
<!-- Labels for the tabs in the customize drawer -->
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index 36d0ee7..8b5e362 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<AppInfo> 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 d0fb5c4..d26577a 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 382c24b..20c9239 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<String> packageNames,
- final ArrayList<AppInfo> appInfos, final UserHandleCompat user) {
+ final ArrayList<AppInfo> 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 54b2008..2d44909 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<PackageInstallInfo> installInfo);
public void updatePackageBadge(String packageName);
public void bindComponentsRemoved(ArrayList<String> packageNames,
- ArrayList<AppInfo> appInfos, UserHandleCompat user);
+ ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
public void bindPackagesUpdated(ArrayList<Object> 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<String> packagesRemoved;
+ final PackageManager manager = context.getPackageManager();
+ final ArrayList<String> packagesRemoved = new ArrayList<String>();
+ final ArrayList<String> packagesUnavailable = new ArrayList<String>();
for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
UserHandleCompat user = entry.getKey();
- packagesRemoved = new ArrayList<String>();
+ 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<N; i++) {
if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
- mBgAllAppsList.removePackage(packages[i], mUser);
+ mBgAllAppsList.removePackage(packages[i], mUser, clearCache);
WidgetPreviewLoader.removePackageFromDb(
mApp.getWidgetPreviewCacheDb(), packages[i]);
}
@@ -3439,7 +3464,7 @@ public class LauncherModel extends BroadcastReceiver
ArrayList<ItemInfo> 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<ShortcutInfo> iconsChanged = new ArrayList<ShortcutInfo>();
HashSet<String> packageSet = new HashSet<String>(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<String> removedPackageNames =
new ArrayList<String>();
- 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<N; i++) {
if (isPackageDisabled(context, packages[i], mUser)) {
removedPackageNames.add(packages[i]);
}
}
}
- // 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<ItemInfo> 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<ItemInfo> 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 daf3434..01f7931 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -88,10 +88,20 @@ 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 e34edf3..db997db 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<String> packages,
+ final UserHandleCompat user, final int reason) {
+ final HashSet<String> packageNames = new HashSet<String>();
+ 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 6efcc00..5858bc8 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 7e5e6bf..e47b9a5 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);