Fix issue #2256270: Package manager sends bad broadcasts when components change
Also reworks this intent protocol a little bit to be much more efficient, only
requiring one broadcast per package.
Change-Id: I580de4843ebd3c7f2e6df7295a2f80d2937cef7c
diff --git a/api/current.xml b/api/current.xml
index 13e4c94..d6af516 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -36136,6 +36136,17 @@
visibility="public"
>
</field>
+<field name="EXTRA_CHANGED_COMPONENT_NAME_LIST"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.intent.extra.changed_component_name_list""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="EXTRA_DATA_REMOVED"
type="java.lang.String"
transient="false"
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c676a5b..398f211 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1275,12 +1275,15 @@
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED";
/**
- * Broadcast Action: An existing application package has been changed (e.g. a component has been
- * enabled or disabled. The data contains the name of the package.
+ * Broadcast Action: An existing application package has been changed (e.g.
+ * a component has been enabled or disabled). The data contains the name of
+ * the package.
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
- * <li> {@link #EXTRA_CHANGED_COMPONENT_NAME} containing the class name of the changed component.
- * <li> {@link #EXTRA_DONT_KILL_APP} containing boolean field to override the default action of restarting the application.
+ * <li> {@link #EXTRA_CHANGED_COMPONENT_NAME_LIST} containing the class name
+ * of the changed components.
+ * <li> {@link #EXTRA_DONT_KILL_APP} containing boolean field to override the
+ * default action of restarting the application.
* </ul>
*
* <p class="note">This is a protected intent that can only be sent
@@ -2101,14 +2104,20 @@
"android.intent.extra.remote_intent_token";
/**
- * Used as an int extra field in {@link android.content.Intent#ACTION_PACKAGE_CHANGED}
- * intent to supply the name of the component that changed.
- *
+ * @Deprecated See {@link #EXTRA_CHANGED_COMPONENT_NAME_LIST}; this field
+ * will contain only the first name in the list.
*/
public static final String EXTRA_CHANGED_COMPONENT_NAME =
"android.intent.extra.changed_component_name";
/**
+ * This field is part of {@link android.content.Intent#ACTION_PACKAGE_CHANGED}
+ * and contains a string array of all of the components that have changed.
+ */
+ public static final String EXTRA_CHANGED_COMPONENT_NAME_LIST =
+ "android.intent.extra.changed_component_name_list";
+
+ /**
* @hide
* Magic extra system code can use when binding, to give a label for
* who it is that has bound to a service. This is an integer giving
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index a83459e..39129d4 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -277,7 +277,8 @@
PackageParser.Package mPlatformPackage;
// Set of pending broadcasts for aggregating enable/disable of components.
- final HashMap<String, String> mPendingBroadcasts = new HashMap<String, String>();
+ final HashMap<String, ArrayList<String>> mPendingBroadcasts
+ = new HashMap<String, ArrayList<String>>();
static final int SEND_PENDING_BROADCAST = 1;
// Delay time in millisecs
static final int BROADCAST_DELAY = 10 * 1000;
@@ -289,30 +290,40 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case SEND_PENDING_BROADCAST : {
+ String packages[];
+ ArrayList components[];
int size = 0;
- String broadcastList[];
- HashMap<String, String> tmpMap;
int uids[];
synchronized (mPackages) {
+ if (mPendingBroadcasts == null) {
+ return;
+ }
size = mPendingBroadcasts.size();
if (size <= 0) {
// Nothing to be done. Just return
return;
}
- broadcastList = new String[size];
- mPendingBroadcasts.keySet().toArray(broadcastList);
- tmpMap = new HashMap<String, String>(mPendingBroadcasts);
+ packages = new String[size];
+ components = new ArrayList[size];
uids = new int[size];
- for (int i = 0; i < size; i++) {
- PackageSetting ps = mSettings.mPackages.get(mPendingBroadcasts.get(broadcastList[i]));
+ Iterator<HashMap.Entry<String, ArrayList<String>>>
+ it = mPendingBroadcasts.entrySet().iterator();
+ int i = 0;
+ while (it.hasNext() && i < size) {
+ HashMap.Entry<String, ArrayList<String>> ent = it.next();
+ packages[i] = ent.getKey();
+ components[i] = ent.getValue();
+ PackageSetting ps = mSettings.mPackages.get(ent.getKey());
uids[i] = (ps != null) ? ps.userId : -1;
+ i++;
}
+ size = i;
mPendingBroadcasts.clear();
}
// Send broadcasts
for (int i = 0; i < size; i++) {
- String className = broadcastList[i];
- sendPackageChangedBroadcast(className, true, tmpMap.get(className), uids[i]);
+ sendPackageChangedBroadcast(packages[i], true,
+ (ArrayList<String>)components[i], uids[i]);
}
break;
}
@@ -5023,8 +5034,9 @@
final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
boolean sendNow = false;
boolean isApp = (className == null);
- String key = isApp ? packageName : className;
+ String componentName = isApp ? packageName : className;
int packageUid = -1;
+ ArrayList<String> components;
synchronized (mPackages) {
pkgSetting = mSettings.mPackages.get(packageName);
if (pkgSetting == null) {
@@ -5064,17 +5076,22 @@
}
mSettings.writeLP();
packageUid = pkgSetting.userId;
+ components = mPendingBroadcasts.get(packageName);
+ boolean newPackage = components == null;
+ if (newPackage) {
+ components = new ArrayList<String>();
+ }
+ if (!components.contains(componentName)) {
+ components.add(componentName);
+ }
if ((flags&PackageManager.DONT_KILL_APP) == 0) {
sendNow = true;
// Purge entry from pending broadcast list if another one exists already
// since we are sending one right away.
- if (mPendingBroadcasts.get(key) != null) {
- mPendingBroadcasts.remove(key);
- // Can ignore empty list since its handled in the handler anyway
- }
+ mPendingBroadcasts.remove(packageName);
} else {
- if (mPendingBroadcasts.get(key) == null) {
- mPendingBroadcasts.put(key, packageName);
+ if (newPackage) {
+ mPendingBroadcasts.put(packageName, components);
}
if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) {
// Schedule a message
@@ -5087,7 +5104,7 @@
try {
if (sendNow) {
sendPackageChangedBroadcast(packageName,
- (flags&PackageManager.DONT_KILL_APP) != 0, key, packageUid);
+ (flags&PackageManager.DONT_KILL_APP) != 0, components, packageUid);
}
} finally {
Binder.restoreCallingIdentity(callingId);
@@ -5095,9 +5112,14 @@
}
private void sendPackageChangedBroadcast(String packageName,
- boolean killFlag, String componentName, int packageUid) {
- Bundle extras = new Bundle(2);
- extras.putString(Intent.EXTRA_CHANGED_COMPONENT_NAME, componentName);
+ boolean killFlag, ArrayList<String> componentNames, int packageUid) {
+ if (false) Log.v(TAG, "Sending package changed: package=" + packageName
+ + " components=" + componentNames);
+ Bundle extras = new Bundle(4);
+ extras.putString(Intent.EXTRA_CHANGED_COMPONENT_NAME, componentNames.get(0));
+ String nameList[] = new String[componentNames.size()];
+ componentNames.toArray(nameList);
+ extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList);
extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag);
extras.putInt(Intent.EXTRA_UID, packageUid);
sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras);