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);
}