Merge "Removing non-preferred activity from resolution set should not disrupt the preferred resolution" into oc-mr1-dev
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2db162d..88d7cf8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7007,22 +7007,44 @@
 
                             // Okay we found a previously set preferred or last chosen app.
                             // If the result set is different from when this
-                            // was created, we need to clear it and re-ask the
-                            // user their preference, if we're looking for an "always" type entry.
+                            // was created, and is not a subset of the preferred set, we need to
+                            // clear it and re-ask the user their preference, if we're looking for
+                            // an "always" type entry.
                             if (always && !pa.mPref.sameSet(query)) {
-                                Slog.i(TAG, "Result set changed, dropping preferred activity for "
-                                        + intent + " type " + resolvedType);
-                                if (DEBUG_PREFERRED) {
-                                    Slog.v(TAG, "Removing preferred activity since set changed "
-                                            + pa.mPref.mComponent);
+                                if (pa.mPref.isSuperset(query)) {
+                                    // some components of the set are no longer present in
+                                    // the query, but the preferred activity can still be reused
+                                    if (DEBUG_PREFERRED) {
+                                        Slog.i(TAG, "Result set changed, but PreferredActivity is"
+                                                + " still valid as only non-preferred components"
+                                                + " were removed for " + intent + " type "
+                                                + resolvedType);
+                                    }
+                                    // remove obsolete components and re-add the up-to-date filter
+                                    PreferredActivity freshPa = new PreferredActivity(pa,
+                                            pa.mPref.mMatch,
+                                            pa.mPref.discardObsoleteComponents(query),
+                                            pa.mPref.mComponent,
+                                            pa.mPref.mAlways);
+                                    pir.removeFilter(pa);
+                                    pir.addFilter(freshPa);
+                                    changed = true;
+                                } else {
+                                    Slog.i(TAG,
+                                            "Result set changed, dropping preferred activity for "
+                                                    + intent + " type " + resolvedType);
+                                    if (DEBUG_PREFERRED) {
+                                        Slog.v(TAG, "Removing preferred activity since set changed "
+                                                + pa.mPref.mComponent);
+                                    }
+                                    pir.removeFilter(pa);
+                                    // Re-add the filter as a "last chosen" entry (!always)
+                                    PreferredActivity lastChosen = new PreferredActivity(
+                                            pa, pa.mPref.mMatch, null, pa.mPref.mComponent, false);
+                                    pir.addFilter(lastChosen);
+                                    changed = true;
+                                    return null;
                                 }
-                                pir.removeFilter(pa);
-                                // Re-add the filter as a "last chosen" entry (!always)
-                                PreferredActivity lastChosen = new PreferredActivity(
-                                        pa, pa.mPref.mMatch, null, pa.mPref.mComponent, false);
-                                pir.addFilter(lastChosen);
-                                changed = true;
-                                return null;
                             }
 
                             // Yay! Either the set matched or we're looking for the last chosen
diff --git a/services/core/java/com/android/server/pm/PreferredComponent.java b/services/core/java/com/android/server/pm/PreferredComponent.java
index 8e2e0cd..0f4df97 100644
--- a/services/core/java/com/android/server/pm/PreferredComponent.java
+++ b/services/core/java/com/android/server/pm/PreferredComponent.java
@@ -30,6 +30,7 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.List;
 
 public class PreferredComponent {
@@ -241,6 +242,54 @@
         return numMatch == NS;
     }
 
+    public boolean isSuperset(List<ResolveInfo> query) {
+        if (mSetPackages == null) {
+            return query == null;
+        }
+        if (query == null) {
+            return true;
+        }
+        final int NQ = query.size();
+        final int NS = mSetPackages.length;
+        if (NS < NQ) {
+            return false;
+        }
+        for (int i=0; i<NQ; i++) {
+            ResolveInfo ri = query.get(i);
+            ActivityInfo ai = ri.activityInfo;
+            boolean foundMatch = false;
+            for (int j=0; j<NS; j++) {
+                if (mSetPackages[j].equals(ai.packageName) && mSetClasses[j].equals(ai.name)) {
+                    foundMatch = true;
+                    break;
+                }
+            }
+            if (!foundMatch) return false;
+        }
+        return true;
+    }
+
+    /** Returns components from mSetPackages that are present in query. */
+    public ComponentName[] discardObsoleteComponents(List<ResolveInfo> query) {
+        if (mSetPackages == null || query == null) {
+            return new ComponentName[0];
+        }
+        final int NQ = query.size();
+        final int NS = mSetPackages.length;
+        ArrayList<ComponentName> aliveComponents = new ArrayList<>();
+        for (int i = 0; i < NQ; i++) {
+            ResolveInfo ri = query.get(i);
+            ActivityInfo ai = ri.activityInfo;
+            for (int j = 0; j < NS; j++) {
+                if (mSetPackages[j].equals(ai.packageName) && mSetClasses[j].equals(ai.name)) {
+                    aliveComponents.add(new ComponentName(mSetPackages[j], mSetClasses[j]));
+                    break;
+                }
+            }
+        }
+        return aliveComponents.toArray(new ComponentName[aliveComponents.size()]);
+    }
+
     public void dump(PrintWriter out, String prefix, Object ident) {
         out.print(prefix); out.print(
                 Integer.toHexString(System.identityHashCode(ident)));