Improve configuration of default preferred apps.

The file that defines default preferred apps is now more
robust.  It is no longer a raw dump of the package
manager settings, but instead a more general list of a
target activity and filter.  When reading it, the remaining
information (match value, set of potential matches) is
determined dynamically.

Change-Id: I0edc6e0d2ed3dd2a6e2238992f18f7fc1f51d8d4
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 0445b39..a368451 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -210,6 +210,8 @@
     
     List<PackageInfo> getPreferredPackages(int flags);
 
+    void resetPreferredActivities(int userId);
+
     void addPreferredActivity(in IntentFilter filter, int match,
             in ComponentName[] set, in ComponentName activity, int userId);
 
diff --git a/core/java/com/android/internal/util/FastXmlSerializer.java b/core/java/com/android/internal/util/FastXmlSerializer.java
index 592a8fa..99eea15 100644
--- a/core/java/com/android/internal/util/FastXmlSerializer.java
+++ b/core/java/com/android/internal/util/FastXmlSerializer.java
@@ -50,6 +50,8 @@
 
     private static final int BUFFER_LEN = 8192;
 
+    private static String sSpace = "                                                              ";
+
     private final char[] mText = new char[BUFFER_LEN];
     private int mPos;
 
@@ -59,8 +61,12 @@
     private CharsetEncoder mCharset;
     private ByteBuffer mBytes = ByteBuffer.allocate(BUFFER_LEN);
 
+    private boolean mIndent = false;
     private boolean mInTag;
 
+    private int mNesting = 0;
+    private boolean mLineStart = true;
+
     private void append(char c) throws IOException {
         int pos = mPos;
         if (pos >= (BUFFER_LEN-1)) {
@@ -113,6 +119,14 @@
         append(str, 0, str.length());
     }
 
+    private void appendIndent(int indent) throws IOException {
+        indent *= 4;
+        if (indent > sSpace.length()) {
+            indent = sSpace.length();
+        }
+        append(sSpace, 0, indent);
+    }
+
     private void escapeAndAppendString(final String string) throws IOException {
         final int N = string.length();
         final char NE = (char)ESCAPE_TABLE.length;
@@ -161,6 +175,7 @@
 
         escapeAndAppendString(value);
         append('"');
+        mLineStart = false;
         return this;
     }
 
@@ -185,9 +200,13 @@
 
     public XmlSerializer endTag(String namespace, String name) throws IOException,
             IllegalArgumentException, IllegalStateException {
+        mNesting--;
         if (mInTag) {
             append(" />\n");
         } else {
+            if (mIndent && mLineStart) {
+                appendIndent(mNesting);
+            }
             append("</");
             if (namespace != null) {
                 append(namespace);
@@ -196,6 +215,7 @@
             append(name);
             append(">\n");
         }
+        mLineStart = true;
         mInTag = false;
         return this;
     }
@@ -278,6 +298,7 @@
     public void setFeature(String name, boolean state) throws IllegalArgumentException,
             IllegalStateException {
         if (name.equals("http://xmlpull.org/v1/doc/features.html#indent-output")) {
+            mIndent = true;
             return;
         }
         throw new UnsupportedOperationException();
@@ -325,6 +346,7 @@
             IllegalArgumentException, IllegalStateException {
         append("<?xml version='1.0' encoding='utf-8' standalone='"
                 + (standalone ? "yes" : "no") + "' ?>\n");
+        mLineStart = true;
     }
 
     public XmlSerializer startTag(String namespace, String name) throws IOException,
@@ -332,6 +354,10 @@
         if (mInTag) {
             append(">\n");
         }
+        if (mIndent) {
+            appendIndent(mNesting);
+        }
+        mNesting++;
         append('<');
         if (namespace != null) {
             append(namespace);
@@ -339,6 +365,7 @@
         }
         append(name);
         mInTag = true;
+        mLineStart = false;
         return this;
     }
 
@@ -349,6 +376,9 @@
             mInTag = false;
         }
         escapeAndAppendString(buf, start, len);
+        if (mIndent) {
+            mLineStart = buf[start+len-1] == '\n';
+        }
         return this;
     }
 
@@ -359,6 +389,9 @@
             mInTag = false;
         }
         escapeAndAppendString(text);
+        if (mIndent) {
+            mLineStart = text.charAt(text.length()-1) == '\n';
+        }
         return this;
     }
 
diff --git a/services/java/com/android/server/PreferredComponent.java b/services/java/com/android/server/PreferredComponent.java
index 718b05d..bb22545 100644
--- a/services/java/com/android/server/PreferredComponent.java
+++ b/services/java/com/android/server/PreferredComponent.java
@@ -164,17 +164,19 @@
         return mParseError;
     }
 
-    public void writeToXml(XmlSerializer serializer) throws IOException {
+    public void writeToXml(XmlSerializer serializer, boolean full) throws IOException {
         final int NS = mSetClasses != null ? mSetClasses.length : 0;
         serializer.attribute(null, "name", mShortComponent);
-        if (mMatch != 0) {
-            serializer.attribute(null, "match", Integer.toHexString(mMatch));
-        }
-        serializer.attribute(null, "set", Integer.toString(NS));
-        for (int s=0; s<NS; s++) {
-            serializer.startTag(null, "set");
-            serializer.attribute(null, "name", mSetComponents[s]);
-            serializer.endTag(null, "set");
+        if (full) {
+            if (mMatch != 0) {
+                serializer.attribute(null, "match", Integer.toHexString(mMatch));
+            }
+            serializer.attribute(null, "set", Integer.toString(NS));
+            for (int s=0; s<NS; s++) {
+                serializer.startTag(null, "set");
+                serializer.attribute(null, "name", mSetComponents[s]);
+                serializer.endTag(null, "set");
+            }
         }
     }
 
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 626002d..eadf196 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -173,7 +173,7 @@
 public class PackageManagerService extends IPackageManager.Stub {
     static final String TAG = "PackageManager";
     static final boolean DEBUG_SETTINGS = false;
-    private static final boolean DEBUG_PREFERRED = false;
+    static final boolean DEBUG_PREFERRED = true;
     static final boolean DEBUG_UPGRADE = false;
     private static final boolean DEBUG_INSTALL = false;
     private static final boolean DEBUG_REMOVE = false;
@@ -1021,7 +1021,7 @@
 
             readPermissions();
 
-            mRestoredSettings = mSettings.readLPw(sUserManager.getUsers(false),
+            mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
                     mSdkVersion, mOnlyCore);
             long startTime = SystemClock.uptimeMillis();
 
@@ -4967,7 +4967,7 @@
         ps.haveGids = true;
     }
     
-    private final class ActivityIntentResolver
+    final class ActivityIntentResolver
             extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> {
         public List<ResolveInfo> queryIntent(Intent intent, String resolvedType,
                 boolean defaultOnly, int userId) {
@@ -8830,8 +8830,10 @@
                 }
             }
 
-            if (clearPackagePreferredActivitiesLPw(packageName, UserHandle.getCallingUserId())) {
-                scheduleWriteSettingsLocked();            
+            int user = UserHandle.getCallingUserId();
+            if (clearPackagePreferredActivitiesLPw(packageName, user)) {
+                mSettings.writePackageRestrictionsLPr(user);
+                scheduleWriteSettingsLocked();
             }
         }
     }
@@ -8849,7 +8851,8 @@
             Iterator<PreferredActivity> it = pir.filterIterator();
             while (it.hasNext()) {
                 PreferredActivity pa = it.next();
-                if (pa.mPref.mComponent.getPackageName().equals(packageName)) {
+                if (packageName == null ||
+                        pa.mPref.mComponent.getPackageName().equals(packageName)) {
                     if (removed == null) {
                         removed = new ArrayList<PreferredActivity>();
                     }
@@ -8862,12 +8865,24 @@
                     pir.removeFilter(pa);
                 }
                 changed = true;
-                mSettings.writePackageRestrictionsLPr(thisUserId);
             }
         }
         return changed;
     }
 
+    public void resetPreferredActivities(int userId) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
+        // writer
+        synchronized (mPackages) {
+            int user = UserHandle.getCallingUserId();
+            clearPackagePreferredActivitiesLPw(null, user);
+            mSettings.readDefaultPreferredAppsLPw(this, user);
+            mSettings.writePackageRestrictionsLPr(user);
+            scheduleWriteSettingsLocked();
+        }
+    }
+
     public int getPreferredActivities(List<IntentFilter> outFilters,
             List<ComponentName> outActivities, String packageName) {
 
@@ -9254,6 +9269,7 @@
         }
 
         DumpState dumpState = new DumpState();
+        boolean fullPreferred = false;
         
         String packageName = null;
         
@@ -9277,7 +9293,7 @@
                 pw.println("    r[esolvers]: dump intent resolvers");
                 pw.println("    perm[issions]: dump permissions");
                 pw.println("    pref[erred]: print preferred package settings");
-                pw.println("    preferred-xml: print preferred package settings as xml");
+                pw.println("    preferred-xml [--full]: print preferred package settings as xml");
                 pw.println("    prov[iders]: dump content providers");
                 pw.println("    p[ackages]: dump installed packages");
                 pw.println("    s[hared-users]: dump shared user IDs");
@@ -9311,6 +9327,10 @@
                 dumpState.setDump(DumpState.DUMP_PREFERRED);
             } else if ("preferred-xml".equals(cmd)) {
                 dumpState.setDump(DumpState.DUMP_PREFERRED_XML);
+                opti++;
+                if (opti < args.length && "--full".equals(args[opti])) {
+                    fullPreferred = true;
+                }
             } else if ("p".equals(cmd) || "packages".equals(cmd)) {
                 dumpState.setDump(DumpState.DUMP_PACKAGES);
             } else if ("s".equals(cmd) || "shared-users".equals(cmd)) {
@@ -9405,7 +9425,7 @@
                     serializer.startDocument(null, true);
                     serializer.setFeature(
                             "http://xmlpull.org/v1/doc/features.html#indent-output", true);
-                    mSettings.writePreferredActivitiesLPr(serializer, 0);
+                    mSettings.writePreferredActivitiesLPr(serializer, 0, fullPreferred);
                     serializer.endDocument();
                     serializer.flush();
                 } catch (IllegalArgumentException e) {
@@ -10158,7 +10178,7 @@
     /** Called by UserManagerService */
     void createNewUserLILPw(int userHandle, File path) {
         if (mInstaller != null) {
-            mSettings.createNewUserLILPw(mInstaller, userHandle, path);
+            mSettings.createNewUserLILPw(this, mInstaller, userHandle, path);
         }
     }
 
diff --git a/services/java/com/android/server/pm/PreferredActivity.java b/services/java/com/android/server/pm/PreferredActivity.java
index dbf56ef..c655bb1 100644
--- a/services/java/com/android/server/pm/PreferredActivity.java
+++ b/services/java/com/android/server/pm/PreferredActivity.java
@@ -46,8 +46,8 @@
         mPref = new PreferredComponent(this, parser);
     }
 
-    public void writeToXml(XmlSerializer serializer) throws IOException {
-        mPref.writeToXml(serializer);
+    public void writeToXml(XmlSerializer serializer, boolean full) throws IOException {
+        mPref.writeToXml(serializer, full);
         serializer.startTag(null, "filter");
             super.writeToXml(serializer);
         serializer.endTag(null, "filter");
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index e336524..13f514b 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -23,6 +23,12 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.PatternMatcher;
+import android.util.LogPrinter;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.XmlUtils;
@@ -972,14 +978,14 @@
         return components;
     }
 
-    void writePreferredActivitiesLPr(XmlSerializer serializer, int userId)
+    void writePreferredActivitiesLPr(XmlSerializer serializer, int userId, boolean full)
             throws IllegalArgumentException, IllegalStateException, IOException {
         serializer.startTag(null, "preferred-activities");
         PreferredIntentResolver pir = mPreferredActivities.get(userId);
         if (pir != null) {
             for (final PreferredActivity pa : pir.filterSet()) {
                 serializer.startTag(null, TAG_ITEM);
-                pa.writeToXml(serializer);
+                pa.writeToXml(serializer, full);
                 serializer.endTag(null, TAG_ITEM);
             }
         }
@@ -1072,7 +1078,7 @@
                 }
             }
 
-            writePreferredActivitiesLPr(serializer, userId);
+            writePreferredActivitiesLPr(serializer, userId, true);
 
             serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS);
 
@@ -1557,7 +1563,8 @@
         }
     }
 
-    boolean readLPw(List<UserInfo> users, int sdkVersion, boolean onlyCore) {
+    boolean readLPw(PackageManagerService service, List<UserInfo> users, int sdkVersion,
+            boolean onlyCore) {
         FileInputStream str = null;
         if (mBackupSettingsFilename.exists()) {
             try {
@@ -1588,7 +1595,7 @@
                     PackageManagerService.reportSettingsProblem(Log.INFO,
                             "No settings file; creating initial state");
                     if (!onlyCore) {
-                        readDefaultPreferredAppsLPw(0);
+                        readDefaultPreferredAppsLPw(service, 0);
                     }
                     mInternalSdkPlatform = mExternalSdkPlatform = sdkVersion;
                     return false;
@@ -1771,7 +1778,7 @@
         return true;
     }
 
-    private void readDefaultPreferredAppsLPw(int userId) {
+    void readDefaultPreferredAppsLPw(PackageManagerService service, int userId) {
         // Read preferred apps from .../etc/preferred-apps directory.
         File preferredDir = new File(Environment.getRootDirectory(), "etc/preferred-apps");
         if (!preferredDir.exists() || !preferredDir.isDirectory()) {
@@ -1793,6 +1800,7 @@
                 continue;
             }
 
+            if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Reading default preferred " + f);
             FileInputStream str = null;
             try {
                 str = new FileInputStream(f);
@@ -1814,7 +1822,7 @@
                             + " does not start with 'preferred-activities'");
                     continue;
                 }
-                readPreferredActivitiesLPw(parser, userId);
+                readDefaultPreferredActivitiesLPw(service, parser, userId);
             } catch (XmlPullParserException e) {
                 Slog.w(TAG, "Error reading apps file " + f, e);
             } catch (IOException e) {
@@ -1830,6 +1838,112 @@
         }
     }
 
+    private void readDefaultPreferredActivitiesLPw(PackageManagerService service,
+            XmlPullParser parser, int userId)
+            throws XmlPullParserException, IOException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals(TAG_ITEM)) {
+                PreferredActivity tmpPa = new PreferredActivity(parser);
+                if (tmpPa.mPref.getParseError() == null) {
+                    // The initial preferences only specify the target activity
+                    // component and intent-filter, not the set of matches.  So we
+                    // now need to query for the matches to build the correct
+                    // preferred activity entry.
+                    if (PackageManagerService.DEBUG_PREFERRED) {
+                        Log.d(TAG, "Processing preferred:");
+                        tmpPa.dump(new LogPrinter(Log.DEBUG, TAG), "  ");
+                    }
+                    final ComponentName cn = tmpPa.mPref.mComponent;
+                    Intent intent = new Intent();
+                    int flags = 0;
+                    intent.setAction(tmpPa.getAction(0));
+                    for (int i=0; i<tmpPa.countCategories(); i++) {
+                        String cat = tmpPa.getCategory(i);
+                        if (cat.equals(Intent.CATEGORY_DEFAULT)) {
+                            flags |= PackageManager.MATCH_DEFAULT_ONLY;
+                        } else {
+                            intent.addCategory(cat);
+                        }
+                    }
+                    if (tmpPa.countDataSchemes() > 0) {
+                        Uri.Builder builder = new Uri.Builder();
+                        builder.scheme(tmpPa.getDataScheme(0));
+                        if (tmpPa.countDataAuthorities() > 0) {
+                            IntentFilter.AuthorityEntry auth = tmpPa.getDataAuthority(0);
+                            if (auth.getHost() != null) {
+                                builder.authority(auth.getHost());
+                            }
+                        }
+                        if (tmpPa.countDataPaths() > 0) {
+                            PatternMatcher path = tmpPa.getDataPath(0);
+                            builder.path(path.getPath());
+                        }
+                        intent.setData(builder.build());
+                    } else if (tmpPa.countDataTypes() > 0) {
+                        intent.setType(tmpPa.getDataType(0));
+                    }
+                    List<ResolveInfo> ri = service.mActivities.queryIntent(intent,
+                            intent.getType(), flags, 0);
+                    if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Queried " + intent
+                            + " results: " + ri);
+                    int match = 0;
+                    if (ri != null && ri.size() > 1) {
+                        boolean haveAct = false;
+                        boolean haveNonSys = false;
+                        ComponentName[] set = new ComponentName[ri.size()];
+                        for (int i=0; i<ri.size(); i++) {
+                            ActivityInfo ai = ri.get(i).activityInfo;
+                            set[i] = new ComponentName(ai.packageName, ai.name);
+                            if ((ai.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {
+                                // If any of the matches are not system apps, then
+                                // there is a third party app that is now an option...
+                                // so don't set a default since we don't want to hide it.
+                                if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Result "
+                                        + ai.packageName + "/" + ai.name + ": non-system!");
+                                haveNonSys = true;
+                                break;
+                            } else if (cn.getPackageName().equals(ai.packageName)
+                                    && cn.getClassName().equals(ai.name)) {
+                                if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Result "
+                                        + ai.packageName + "/" + ai.name + ": default!");
+                                haveAct = true;
+                                match = ri.get(i).match;
+                            } else {
+                                if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Result "
+                                        + ai.packageName + "/" + ai.name + ": skipped");
+                            }
+                        }
+                        if (haveAct && !haveNonSys) {
+                            PreferredActivity pa = new PreferredActivity(tmpPa, match, set,
+                                    tmpPa.mPref.mComponent);
+                            editPreferredActivitiesLPw(userId).addFilter(pa);
+                        } else if (!haveNonSys) {
+                            Slog.w(TAG, "No component found for default preferred activity "
+                                    + tmpPa.mPref.mComponent);
+                        }
+                    }
+                } else {
+                    PackageManagerService.reportSettingsProblem(Log.WARN,
+                            "Error in package manager settings: <preferred-activity> "
+                                    + tmpPa.mPref.getParseError() + " at "
+                                    + parser.getPositionDescription());
+                }
+            } else {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Unknown element under <preferred-activities>: " + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
     private int readInt(XmlPullParser parser, String ns, String name, int defValue) {
         String v = parser.getAttributeValue(ns, name);
         try {
@@ -2329,7 +2443,8 @@
         }
     }
 
-    void createNewUserLILPw(Installer installer, int userHandle, File path) {
+    void createNewUserLILPw(PackageManagerService service, Installer installer,
+            int userHandle, File path) {
         path.mkdir();
         FileUtils.setPermissions(path.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
                 | FileUtils.S_IXOTH, -1, -1);
@@ -2340,7 +2455,7 @@
             installer.createUserData(ps.name,
                     UserHandle.getUid(userHandle, ps.appId), userHandle);
         }
-        readDefaultPreferredAppsLPw(userHandle);
+        readDefaultPreferredAppsLPw(service, userHandle);
         writePackageRestrictionsLPr(userHandle);
     }