summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabrice Di Meglio <fdimeglio@google.com>2014-11-19 17:12:32 -0800
committerFabrice Di Meglio <fdimeglio@google.com>2015-03-30 10:58:35 -0700
commit1c1b47125da018b44240739db75f8898e064a948 (patch)
treec2c4b241798ae9e5d61fd09955b8c0d2704bb133
parent523fe91af4baf26cd26e46c1418a072574959b73 (diff)
downloadframeworks_base-1c1b47125da018b44240739db75f8898e064a948.zip
frameworks_base-1c1b47125da018b44240739db75f8898e064a948.tar.gz
frameworks_base-1c1b47125da018b44240739db75f8898e064a948.tar.bz2
Add IntentFilter auto verification
The purpose of this feature is to prompt the Disambiguation dialog to Users as less as possible. - add the new "autoVerify" property to the IntentFilter class - add new APIs to PackageManager: verifyIntentFilter(int, int, List<String>), getIntentVerificationStatus(String, int), updateIntentVerificationStatus(String, int, int), getIntentFilterVerifications(String) for supporting IntentFilter verification - add support for multi-user - update PackageManager for IntentFilter verification: basically when we are installing a new package, ask for verification of all domains from the IntentFilters that have the "autoVerify" to true. This means that the PackageManager will send a well defined protected broadcast (with a new INTENT_FILTER_NEEDS_VERIFICATION action) to an IntentFilter verifier to do the real job of verification. We are passing in the broadcast Intent all the necessary data for doing the verification. The PackageManager will receive as response the result code of the domain verifications and, if needed, the list of domains that have failed the verification. - add a new INTENT_FILTER_VERIFICATION_AGENT permission that needs to be set by an intent filter verifier to be considered as a trustable party by the PackageManager. - add also a new BIND_INTENT_FILTER_VERIFIER permission for securing the binding between the PackageManager and a service doing the intent filter verifications. - add ResolveInfo filterNeedsVerification which is a boolean to knows if the IntentFilter is of a type that needs a verification (action VIEW, category BROWABLE, HTTP/HTTPS data URI) - add new "domain-preferred-apps" / "d" dump command for listing the prefered Apps for all domains - add new "intent-filter-verifiers" / "ivf" command for listing the IntentFilterVerifier used - introduce the IntentVerificationService which is a basic service for verifying IntentFilters. This service will send HTTPS requests to the domain declared in the IntentFilter(s) for doing the verification. This service has a low priority level so that it can be replaced by a more sophisticated one if needed. This service is updating the PackageManager intent verification states thru the updateIntentVerificationStatus(...) API. - update MockPackageManager Change-Id: I0bfed193d0bf1f7c7ac79f6c1b160b7ab93b5fb5
-rw-r--r--api/current.txt24
-rw-r--r--api/system-current.txt26
-rw-r--r--core/java/android/app/ApplicationPackageManager.java40
-rw-r--r--core/java/android/content/Intent.java13
-rw-r--r--core/java/android/content/IntentFilter.java148
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl6
-rw-r--r--core/java/android/content/pm/IntentFilterVerificationInfo.aidl19
-rw-r--r--core/java/android/content/pm/IntentFilterVerificationInfo.java224
-rw-r--r--core/java/android/content/pm/PackageManager.java181
-rw-r--r--core/java/android/content/pm/PackageParser.java18
-rw-r--r--core/java/android/content/pm/PackageUserState.java9
-rw-r--r--core/java/android/content/pm/ResolveInfo.java8
-rw-r--r--core/java/android/provider/Settings.java2
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java31
-rw-r--r--core/res/AndroidManifest.xml18
-rw-r--r--core/res/res/values/attrs_manifest.xml13
-rw-r--r--core/res/res/values/public.xml4
-rw-r--r--core/res/res/values/strings.xml16
-rw-r--r--packages/IntentFilterVerifier/Android.mk22
-rw-r--r--packages/IntentFilterVerifier/AndroidManifest.xml44
-rw-r--r--packages/IntentFilterVerifier/CleanSpec.mk49
-rw-r--r--packages/IntentFilterVerifier/proguard.flags1
-rw-r--r--packages/IntentFilterVerifier/res/values/strings.xml23
-rw-r--r--packages/IntentFilterVerifier/src/com/android/verifier/intentfilter/IntentVerificationReceiver.java90
-rw-r--r--packages/IntentFilterVerifier/src/com/android/verifier/intentfilter/IntentVerificationRequest.java29
-rw-r--r--packages/IntentFilterVerifier/src/com/android/verifier/intentfilter/IntentVerificationService.java468
-rw-r--r--services/core/java/com/android/server/IntentResolver.java26
-rw-r--r--services/core/java/com/android/server/pm/IntentFilterVerificationKey.java63
-rw-r--r--services/core/java/com/android/server/pm/IntentFilterVerificationResponse.java43
-rw-r--r--services/core/java/com/android/server/pm/IntentFilterVerificationState.java125
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java618
-rw-r--r--services/core/java/com/android/server/pm/PackageSettingBase.java30
-rw-r--r--services/core/java/com/android/server/pm/Settings.java153
-rw-r--r--test-runner/src/android/test/mock/MockPackageManager.java33
34 files changed, 2580 insertions, 37 deletions
diff --git a/api/current.txt b/api/current.txt
index ae828ce..fb9956f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -330,6 +330,7 @@ package android {
field public static final int autoStart = 16843445; // 0x10102b5
field public static final deprecated int autoText = 16843114; // 0x101016a
field public static final int autoUrlDetect = 16843404; // 0x101028c
+ field public static final int autoVerify = 16844010; // 0x10104ea
field public static final int background = 16842964; // 0x10100d4
field public static final int backgroundDimAmount = 16842802; // 0x1010032
field public static final int backgroundDimEnabled = 16843295; // 0x101021f
@@ -8253,6 +8254,8 @@ package android.content {
field public static final int NO_MATCH_CATEGORY = -4; // 0xfffffffc
field public static final int NO_MATCH_DATA = -2; // 0xfffffffe
field public static final int NO_MATCH_TYPE = -1; // 0xffffffff
+ field public static final java.lang.String SCHEME_HTTP = "http";
+ field public static final java.lang.String SCHEME_HTTPS = "https";
field public static final int SYSTEM_HIGH_PRIORITY = 1000; // 0x3e8
field public static final int SYSTEM_LOW_PRIORITY = -1000; // 0xfffffc18
}
@@ -8832,6 +8835,25 @@ package android.content.pm {
field public java.lang.String targetPackage;
}
+ public final class IntentFilterVerificationInfo implements android.os.Parcelable {
+ ctor public IntentFilterVerificationInfo();
+ ctor public IntentFilterVerificationInfo(java.lang.String, java.lang.String[]);
+ ctor public IntentFilterVerificationInfo(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ ctor public IntentFilterVerificationInfo(android.os.Parcel);
+ method public int describeContents();
+ method public java.lang.String[] getDomains();
+ method public java.lang.String getDomainsString();
+ method public java.lang.String getPackageName();
+ method public int getStatus();
+ method public java.lang.String getStatusString();
+ method public static java.lang.String getStatusStringFromValue(int);
+ method public void readFromXml(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public void setStatus(int);
+ method public void writeToParcel(android.os.Parcel, int);
+ method public void writeToXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
+ field public static final android.os.Parcelable.Creator<android.content.pm.IntentFilterVerificationInfo> CREATOR;
+ }
+
public class LabeledIntent extends android.content.Intent {
ctor public LabeledIntent(android.content.Intent, java.lang.String, int, int);
ctor public LabeledIntent(android.content.Intent, java.lang.String, java.lang.CharSequence, int);
@@ -9060,6 +9082,7 @@ package android.content.pm {
method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public abstract java.lang.String getInstallerPackageName(java.lang.String);
method public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
method public abstract android.content.Intent getLaunchIntentForPackage(java.lang.String);
method public abstract android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
method public abstract java.lang.String getNameForUid(int);
@@ -30468,6 +30491,7 @@ package android.test.mock {
method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public java.lang.String getInstallerPackageName(java.lang.String);
method public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
method public android.content.Intent getLaunchIntentForPackage(java.lang.String);
method public android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
method public java.lang.String getNameForUid(int);
diff --git a/api/system-current.txt b/api/system-current.txt
index f980233..921fc1a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -103,6 +103,7 @@ package android {
field public static final java.lang.String INSTALL_LOCATION_PROVIDER = "android.permission.INSTALL_LOCATION_PROVIDER";
field public static final java.lang.String INSTALL_PACKAGES = "android.permission.INSTALL_PACKAGES";
field public static final java.lang.String INSTALL_SHORTCUT = "com.android.launcher.permission.INSTALL_SHORTCUT";
+ field public static final java.lang.String INTENT_FILTER_VERIFICATION_AGENT = "android.permission.INTENT_FILTER_VERIFICATION_AGENT";
field public static final java.lang.String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS";
field public static final java.lang.String INTERNAL_SYSTEM_WINDOW = "android.permission.INTERNAL_SYSTEM_WINDOW";
field public static final java.lang.String INTERNET = "android.permission.INTERNET";
@@ -401,6 +402,7 @@ package android {
field public static final int autoStart = 16843445; // 0x10102b5
field public static final deprecated int autoText = 16843114; // 0x101016a
field public static final int autoUrlDetect = 16843404; // 0x101028c
+ field public static final int autoVerify = 16844010; // 0x10104ea
field public static final int background = 16842964; // 0x10100d4
field public static final int backgroundDimAmount = 16842802; // 0x1010032
field public static final int backgroundDimEnabled = 16843295; // 0x101021f
@@ -8175,6 +8177,7 @@ package android.content {
field public static final java.lang.String ACTION_INSERT = "android.intent.action.INSERT";
field public static final java.lang.String ACTION_INSERT_OR_EDIT = "android.intent.action.INSERT_OR_EDIT";
field public static final java.lang.String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE";
+ field public static final java.lang.String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
field public static final java.lang.String ACTION_LOCALE_CHANGED = "android.intent.action.LOCALE_CHANGED";
field public static final java.lang.String ACTION_MAIN = "android.intent.action.MAIN";
field public static final java.lang.String ACTION_MANAGED_PROFILE_ADDED = "android.intent.action.MANAGED_PROFILE_ADDED";
@@ -8472,6 +8475,8 @@ package android.content {
field public static final int NO_MATCH_CATEGORY = -4; // 0xfffffffc
field public static final int NO_MATCH_DATA = -2; // 0xfffffffe
field public static final int NO_MATCH_TYPE = -1; // 0xffffffff
+ field public static final java.lang.String SCHEME_HTTP = "http";
+ field public static final java.lang.String SCHEME_HTTPS = "https";
field public static final int SYSTEM_HIGH_PRIORITY = 1000; // 0x3e8
field public static final int SYSTEM_LOW_PRIORITY = -1000; // 0xfffffc18
}
@@ -9070,6 +9075,25 @@ package android.content.pm {
field public java.lang.String targetPackage;
}
+ public final class IntentFilterVerificationInfo implements android.os.Parcelable {
+ ctor public IntentFilterVerificationInfo();
+ ctor public IntentFilterVerificationInfo(java.lang.String, java.lang.String[]);
+ ctor public IntentFilterVerificationInfo(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ ctor public IntentFilterVerificationInfo(android.os.Parcel);
+ method public int describeContents();
+ method public java.lang.String[] getDomains();
+ method public java.lang.String getDomainsString();
+ method public java.lang.String getPackageName();
+ method public int getStatus();
+ method public java.lang.String getStatusString();
+ method public static java.lang.String getStatusStringFromValue(int);
+ method public void readFromXml(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public void setStatus(int);
+ method public void writeToParcel(android.os.Parcel, int);
+ method public void writeToXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
+ field public static final android.os.Parcelable.Creator<android.content.pm.IntentFilterVerificationInfo> CREATOR;
+ }
+
public class LabeledIntent extends android.content.Intent {
ctor public LabeledIntent(android.content.Intent, java.lang.String, int, int);
ctor public LabeledIntent(android.content.Intent, java.lang.String, java.lang.CharSequence, int);
@@ -9304,6 +9328,7 @@ package android.content.pm {
method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public abstract java.lang.String getInstallerPackageName(java.lang.String);
method public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
method public abstract android.content.Intent getLaunchIntentForPackage(java.lang.String);
method public abstract android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
method public abstract java.lang.String getNameForUid(int);
@@ -32903,6 +32928,7 @@ package android.test.mock {
method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public java.lang.String getInstallerPackageName(java.lang.String);
method public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
method public android.content.Intent getLaunchIntentForPackage(java.lang.String);
method public android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
method public java.lang.String getNameForUid(int);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 6d74905..6ec5457 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -36,6 +36,7 @@ import android.content.pm.IPackageManager;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
+import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
import android.content.pm.ManifestDigest;
import android.content.pm.PackageInfo;
@@ -1309,6 +1310,45 @@ final class ApplicationPackageManager extends PackageManager {
}
@Override
+ public void verifyIntentFilter(int id, int verificationCode, List<String> outFailedDomains) {
+ try {
+ mPM.verifyIntentFilter(id, verificationCode, outFailedDomains);
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ }
+
+ @Override
+ public int getIntentVerificationStatus(String packageName, int userId) {
+ try {
+ return mPM.getIntentVerificationStatus(packageName, userId);
+ } catch (RemoteException e) {
+ // Should never happen!
+ return PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
+ }
+
+ @Override
+ public boolean updateIntentVerificationStatus(String packageName, int status, int userId) {
+ try {
+ return mPM.updateIntentVerificationStatus(packageName, status, userId);
+ } catch (RemoteException e) {
+ // Should never happen!
+ return false;
+ }
+ }
+
+ @Override
+ public List<IntentFilterVerificationInfo> getIntentFilterVerifications(String packageName) {
+ try {
+ return mPM.getIntentFilterVerifications(packageName);
+ } catch (RemoteException e) {
+ // Should never happen!
+ return null;
+ }
+ }
+
+ @Override
public void setInstallerPackageName(String targetPackage,
String installerPackageName) {
try {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 030b770..7a99a79 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1911,6 +1911,19 @@ public class Intent implements Parcelable, Cloneable {
public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED";
/**
+ * Broadcast Action: Sent to the system intent filter verifier when an intent filter
+ * needs to be verified. The data contains the filter data hosts to be verified against.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
+
+ /**
* Broadcast Action: Resources for a set of packages (which were
* previously unavailable) are currently
* available since the media on which they exist is available.
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 1240a23..590d791 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -16,10 +16,12 @@
package android.content;
+import android.content.pm.PackageParser;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PatternMatcher;
+import android.text.TextUtils;
import android.util.AndroidException;
import android.util.Log;
import android.util.Printer;
@@ -150,6 +152,7 @@ public class IntentFilter implements Parcelable {
private static final String CAT_STR = "cat";
private static final String NAME_STR = "name";
private static final String ACTION_STR = "action";
+ private static final String AUTO_VERIFY_STR = "autoVerify";
/**
* The filter {@link #setPriority} value at which system high-priority
@@ -247,6 +250,19 @@ public class IntentFilter implements Parcelable {
*/
public static final int NO_MATCH_CATEGORY = -4;
+ /**
+ * HTTP scheme.
+ *
+ * @see #addDataScheme(String)
+ */
+ public static final String SCHEME_HTTP = "http";
+ /**
+ * HTTPS scheme.
+ *
+ * @see #addDataScheme(String)
+ */
+ public static final String SCHEME_HTTPS = "https";
+
private int mPriority;
private final ArrayList<String> mActions;
private ArrayList<String> mCategories = null;
@@ -257,6 +273,13 @@ public class IntentFilter implements Parcelable {
private ArrayList<String> mDataTypes = null;
private boolean mHasPartialTypes = false;
+ private static final int STATE_VERIFY_AUTO = 0x00000001;
+ private static final int STATE_NEED_VERIFY = 0x00000010;
+ private static final int STATE_NEED_VERIFY_CHECKED = 0x00000100;
+ private static final int STATE_VERIFIED = 0x00001000;
+
+ private int mVerifyState;
+
// These functions are the start of more optimized code for managing
// the string sets... not yet implemented.
@@ -326,7 +349,7 @@ public class IntentFilter implements Parcelable {
public MalformedMimeTypeException(String name) {
super(name);
}
- };
+ }
/**
* Create a new IntentFilter instance with a specified action and MIME
@@ -421,6 +444,7 @@ public class IntentFilter implements Parcelable {
mDataPaths = new ArrayList<PatternMatcher>(o.mDataPaths);
}
mHasPartialTypes = o.mHasPartialTypes;
+ mVerifyState = o.mVerifyState;
}
/**
@@ -452,6 +476,94 @@ public class IntentFilter implements Parcelable {
}
/**
+ * Set whether this filter will needs to be automatically verified against its data URIs or not.
+ * The default is false.
+ *
+ * The verification would need to happen only and only if the Intent action is
+ * {@link android.content.Intent#ACTION_VIEW} and the Intent category is
+ * {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent data scheme
+ * is "http" or "https".
+ *
+ * True means that the filter will need to use its data URIs to be verified.
+ *
+ * @param autoVerify The new autoVerify value.
+ *
+ * @see #getAutoVerify()
+ * @see #addAction(String)
+ * @see #getAction(int)
+ * @see #addCategory(String)
+ * @see #getCategory(int)
+ * @see #addDataScheme(String)
+ * @see #getDataScheme(int)
+ *
+ * @hide
+ */
+ public final void setAutoVerify(boolean autoVerify) {
+ mVerifyState &= ~STATE_VERIFY_AUTO;
+ if (autoVerify) mVerifyState |= STATE_VERIFY_AUTO;
+ }
+
+ /**
+ * Return if this filter will needs to be automatically verified again its data URIs or not.
+ *
+ * @return True if the filter will needs to be automatically verified. False otherwise.
+ *
+ * @see #setAutoVerify(boolean)
+ *
+ * @hide
+ */
+ public final boolean getAutoVerify() {
+ return ((mVerifyState & STATE_VERIFY_AUTO) == 1);
+ }
+
+ /**
+ * Return if this filter needs to be automatically verified again its data URIs or not.
+ *
+ * @return True if the filter needs to be automatically verified. False otherwise.
+ *
+ * This will check if if the Intent action is {@link android.content.Intent#ACTION_VIEW} and
+ * the Intent category is {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent
+ * data scheme is "http" or "https".
+ *
+ * @see #setAutoVerify(boolean)
+ *
+ * @hide
+ */
+ public final boolean needsVerification() {
+ return hasAction(Intent.ACTION_VIEW) &&
+ hasCategory(Intent.CATEGORY_BROWSABLE) &&
+ (hasDataScheme(SCHEME_HTTP) || hasDataScheme(SCHEME_HTTPS)) &&
+ getAutoVerify();
+ }
+
+ /**
+ * Return if this filter has been verified
+ *
+ * @return true if the filter has been verified or if autoVerify is false.
+ *
+ * @hide
+ */
+ public final boolean isVerified() {
+ if ((mVerifyState & STATE_NEED_VERIFY_CHECKED) == STATE_NEED_VERIFY_CHECKED) {
+ return ((mVerifyState & STATE_NEED_VERIFY) == STATE_NEED_VERIFY);
+ }
+ return false;
+ }
+
+ /**
+ * Set if this filter has been verified
+ *
+ * @param verified true if this filter has been verified. False otherwise.
+ *
+ * @hide
+ */
+ public void setVerified(boolean verified) {
+ mVerifyState |= STATE_NEED_VERIFY_CHECKED;
+ mVerifyState &= ~STATE_VERIFIED;
+ if (verified) mVerifyState |= STATE_VERIFIED;
+ }
+
+ /**
* Add a new Intent action to match against. If any actions are included
* in the filter, then an Intent's action must be one of those values for
* it to match. If no actions are included, the Intent action is ignored.
@@ -1333,6 +1445,7 @@ public class IntentFilter implements Parcelable {
* Write the contents of the IntentFilter as an XML stream.
*/
public void writeToXml(XmlSerializer serializer) throws IOException {
+ serializer.attribute(null, AUTO_VERIFY_STR, Boolean.toString(getAutoVerify()));
int N = countActions();
for (int i=0; i<N; i++) {
serializer.startTag(null, ACTION_STR);
@@ -1407,6 +1520,9 @@ public class IntentFilter implements Parcelable {
public void readFromXml(XmlPullParser parser) throws XmlPullParserException,
IOException {
+ String autoVerify = parser.getAttributeValue(null, AUTO_VERIFY_STR);
+ setAutoVerify(TextUtils.isEmpty(autoVerify) ? false : Boolean.getBoolean(autoVerify));
+
int outerDepth = parser.getDepth();
int type;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1548,6 +1664,11 @@ public class IntentFilter implements Parcelable {
sb.append(", mHasPartialTypes="); sb.append(mHasPartialTypes);
du.println(sb.toString());
}
+ {
+ sb.setLength(0);
+ sb.append(prefix); sb.append("AutoVerify="); sb.append(getAutoVerify());
+ du.println(sb.toString());
+ }
}
public static final Parcelable.Creator<IntentFilter> CREATOR
@@ -1614,6 +1735,7 @@ public class IntentFilter implements Parcelable {
}
dest.writeInt(mPriority);
dest.writeInt(mHasPartialTypes ? 1 : 0);
+ dest.writeInt(getAutoVerify() ? 1 : 0);
}
/**
@@ -1680,6 +1802,7 @@ public class IntentFilter implements Parcelable {
}
mPriority = source.readInt();
mHasPartialTypes = source.readInt() > 0;
+ setAutoVerify(source.readInt() > 0);
}
private final boolean findMimeType(String type) {
@@ -1724,4 +1847,27 @@ public class IntentFilter implements Parcelable {
return false;
}
+
+ /**
+ * @hide
+ */
+ public ArrayList<String> getHostsList() {
+ ArrayList<String> result = new ArrayList<>();
+ Iterator<IntentFilter.AuthorityEntry> it = authoritiesIterator();
+ if (it != null) {
+ while (it.hasNext()) {
+ IntentFilter.AuthorityEntry entry = it.next();
+ result.add(entry.getHost());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public String[] getHosts() {
+ ArrayList<String> list = getHostsList();
+ return list.toArray(new String[list.size()]);
+ }
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index c6d97f1..66b0d1a 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -31,6 +31,7 @@ import android.content.pm.IPackageDeleteObserver2;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
+import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.InstrumentationInfo;
import android.content.pm.KeySet;
import android.content.pm.PackageInfo;
@@ -436,6 +437,11 @@ interface IPackageManager {
void verifyPendingInstall(int id, int verificationCode);
void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay);
+ void verifyIntentFilter(int id, int verificationCode, in List<String> outFailedDomains);
+ int getIntentVerificationStatus(String packageName, int userId);
+ boolean updateIntentVerificationStatus(String packageName, int status, int userId);
+ List<IntentFilterVerificationInfo> getIntentFilterVerifications(String packageName);
+
VerifierDeviceIdentity getVerifierDeviceIdentity();
boolean isFirstBoot();
diff --git a/core/java/android/content/pm/IntentFilterVerificationInfo.aidl b/core/java/android/content/pm/IntentFilterVerificationInfo.aidl
new file mode 100644
index 0000000..00220e5
--- /dev/null
+++ b/core/java/android/content/pm/IntentFilterVerificationInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+parcelable IntentFilterVerificationInfo;
diff --git a/core/java/android/content/pm/IntentFilterVerificationInfo.java b/core/java/android/content/pm/IntentFilterVerificationInfo.java
new file mode 100644
index 0000000..60cb4a8
--- /dev/null
+++ b/core/java/android/content/pm/IntentFilterVerificationInfo.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * The {@link com.android.server.pm.PackageManagerService} maintains some
+ * {@link IntentFilterVerificationInfo}s for each domain / package / class name per user.
+ *
+ * @hide
+ */
+public final class IntentFilterVerificationInfo implements Parcelable {
+ private static final String TAG = IntentFilterVerificationInfo.class.getName();
+
+ private static final String TAG_DOMAIN = "domain";
+ private static final String ATTR_DOMAIN_NAME = "name";
+ private static final String ATTR_PACKAGE_NAME = "packageName";
+ private static final String ATTR_STATUS = "status";
+
+ private String[] mDomains;
+ private String mPackageName;
+ private int mMainStatus;
+
+ public IntentFilterVerificationInfo() {
+ mPackageName = null;
+ mDomains = new String[0];
+ mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
+
+ public IntentFilterVerificationInfo(String packageName, String[] domains) {
+ mPackageName = packageName;
+ mDomains = domains;
+ mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
+
+ public IntentFilterVerificationInfo(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ readFromXml(parser);
+ }
+
+ public IntentFilterVerificationInfo(Parcel source) {
+ readFromParcel(source);
+ }
+
+ public String[] getDomains() {
+ return mDomains;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public int getStatus() {
+ return mMainStatus;
+ }
+
+ public void setStatus(int s) {
+ if (s >= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED &&
+ s <= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
+ mMainStatus = s;
+ } else {
+ Log.w(TAG, "Trying to set a non supported status: " + s);
+ }
+ }
+
+ public String getDomainsString() {
+ StringBuilder sb = new StringBuilder();
+ for (String str : mDomains) {
+ if (sb.length() > 0) {
+ sb.append(" ");
+ }
+ sb.append(str);
+ }
+ return sb.toString();
+ }
+
+ String getStringFromXml(XmlPullParser parser, String attribute, String defaultValue) {
+ String value = parser.getAttributeValue(null, attribute);
+ if (value == null) {
+ String msg = "Missing element under " + TAG +": " + attribute + " at " +
+ parser.getPositionDescription();
+ Log.w(TAG, msg);
+ return defaultValue;
+ } else {
+ return value;
+ }
+ }
+
+ int getIntFromXml(XmlPullParser parser, String attribute, int defaultValue) {
+ String value = parser.getAttributeValue(null, attribute);
+ if (TextUtils.isEmpty(value)) {
+ String msg = "Missing element under " + TAG +": " + attribute + " at " +
+ parser.getPositionDescription();
+ Log.w(TAG, msg);
+ return defaultValue;
+ } else {
+ return Integer.parseInt(value);
+ }
+ }
+
+ public void readFromXml(XmlPullParser parser) throws XmlPullParserException,
+ IOException {
+ mPackageName = getStringFromXml(parser, ATTR_PACKAGE_NAME, null);
+ if (mPackageName == null) {
+ Log.e(TAG, "Package name cannot be null!");
+ }
+ int status = getIntFromXml(parser, ATTR_STATUS, -1);
+ if (status == -1) {
+ Log.e(TAG, "Unknown status value: " + status);
+ }
+ mMainStatus = status;
+
+ ArrayList<String> list = new ArrayList<>();
+ 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_DOMAIN)) {
+ String name = getStringFromXml(parser, ATTR_DOMAIN_NAME, null);
+ if (!TextUtils.isEmpty(name)) {
+ if (list == null) {
+ list = new ArrayList<>();
+ }
+ list.add(name);
+ }
+ } else {
+ Log.w(TAG, "Unknown tag parsing IntentFilter: " + tagName);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ }
+
+ mDomains = list.toArray(new String[list.size()]);
+ }
+
+ public void writeToXml(XmlSerializer serializer) throws IOException {
+ serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName);
+ serializer.attribute(null, ATTR_STATUS, String.valueOf(mMainStatus));
+ for (String str : mDomains) {
+ serializer.startTag(null, TAG_DOMAIN);
+ serializer.attribute(null, ATTR_DOMAIN_NAME, str);
+ serializer.endTag(null, TAG_DOMAIN);
+ }
+ }
+
+ public String getStatusString() {
+ return getStatusStringFromValue(mMainStatus);
+ }
+
+ public static String getStatusStringFromValue(int val) {
+ switch (val) {
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK : return "ask";
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS : return "always";
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER : return "never";
+ default:
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED : return "undefined";
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private void readFromParcel(Parcel source) {
+ mPackageName = source.readString();
+ mMainStatus = source.readInt();
+ mDomains = source.readStringArray();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mPackageName);
+ dest.writeInt(mMainStatus);
+ dest.writeStringArray(mDomains);
+ }
+
+ public static final Creator<IntentFilterVerificationInfo> CREATOR =
+ new Creator<IntentFilterVerificationInfo>() {
+ public IntentFilterVerificationInfo createFromParcel(Parcel source) {
+ return new IntentFilterVerificationInfo(source);
+ }
+ public IntentFilterVerificationInfo[] newArray(int size) {
+ return new IntentFilterVerificationInfo[size];
+ }
+ };
+
+}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f0d1da9..46d6ffb3 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -969,6 +969,60 @@ public abstract class PackageManager {
public static final int VERIFICATION_REJECT = -1;
/**
+ * Used as the {@code verificationCode} argument for
+ * {@link PackageManager#verifyIntentFilter} to indicate that the calling
+ * IntentFilter Verifier confirms that the IntentFilter is verified.
+ *
+ * @hide
+ */
+ public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1;
+
+ /**
+ * Used as the {@code verificationCode} argument for
+ * {@link PackageManager#verifyIntentFilter} to indicate that the calling
+ * IntentFilter Verifier confirms that the IntentFilter is NOT verified.
+ *
+ * @hide
+ */
+ public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1;
+
+ /**
+ * Internal status code to indicate that an IntentFilter verification result is not specified.
+ *
+ * @hide
+ */
+ public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0;
+
+ /**
+ * Used as the {@code status} argument for {@link PackageManager#updateIntentVerificationStatus}
+ * to indicate that the User will always be prompted the Intent Disambiguation Dialog if there
+ * are two or more Intent resolved for the IntentFilter's domain(s).
+ *
+ * @hide
+ */
+ public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1;
+
+ /**
+ * Used as the {@code status} argument for {@link PackageManager#updateIntentVerificationStatus}
+ * to indicate that the User will never be prompted the Intent Disambiguation Dialog if there
+ * are two or more resolution of the Intent. The default App for the domain(s) specified in the
+ * IntentFilter will also ALWAYS be used.
+ *
+ * @hide
+ */
+ public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2;
+
+ /**
+ * Used as the {@code status} argument for {@link PackageManager#updateIntentVerificationStatus}
+ * to indicate that the User may be prompted the Intent Disambiguation Dialog if there
+ * are two or more Intent resolved. The default App for the domain(s) specified in the
+ * IntentFilter will also NEVER be presented to the User.
+ *
+ * @hide
+ */
+ public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3;
+
+ /**
* Can be used as the {@code millisecondsToDelay} argument for
* {@link PackageManager#extendVerificationTimeout}. This is the
* maximum time {@code PackageManager} waits for the verification
@@ -1680,8 +1734,52 @@ public abstract class PackageManager {
= "android.content.pm.extra.VERIFICATION_VERSION_CODE";
/**
- * The action used to request that the user approve a grant permissions
- * request from the application.
+ * Extra field name for the ID of a intent filter pending verification. Passed to
+ * an intent filter verifier and is used to call back to
+ * {@link PackageManager#verifyIntentFilter(int, int)}
+ *
+ * @hide
+ */
+ public static final String EXTRA_INTENT_FILTER_VERIFICATION_ID
+ = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_ID";
+
+ /**
+ * Extra field name for the scheme used for an intent filter pending verification. Passed to
+ * an intent filter verifier and is used to construct the URI to verify against.
+ *
+ * Usually this is "https"
+ *
+ * @hide
+ */
+ public static final String EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME
+ = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_URI_SCHEME";
+
+ /**
+ * Extra field name for the host names to be used for an intent filter pending verification.
+ * Passed to an intent filter verifier and is used to construct the URI to verify the
+ * intent filter.
+ *
+ * This is a space delimited list of hosts.
+ *
+ * @hide
+ */
+ public static final String EXTRA_INTENT_FILTER_VERIFICATION_HOSTS
+ = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_HOSTS";
+
+ /**
+ * Extra field name for the package name to be used for an intent filter pending verification.
+ * Passed to an intent filter verifier and is used to check the verification responses coming
+ * from the hosts. Each host response will need to include the package name of APK containing
+ * the intent filter.
+ *
+ * @hide
+ */
+ public static final String EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME
+ = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_PACKAGE_NAME";
+
+ /**
+ * The action used to request that the user approve a permission request
+ * from the application.
*
* @hide
*/
@@ -3461,6 +3559,85 @@ public abstract class PackageManager {
int verificationCodeAtTimeout, long millisecondsToDelay);
/**
+ * Allows a package listening to the
+ * {@link Intent#ACTION_INTENT_FILTER_NEEDS_VERIFICATION intent filter verification
+ * broadcast} to respond to the package manager. The response must include
+ * the {@code verificationCode} which is one of
+ * {@link PackageManager#INTENT_FILTER_VERIFICATION_SUCCESS} or
+ * {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}.
+ *
+ * @param verificationId pending package identifier as passed via the
+ * {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra.
+ * @param verificationCode either {@link PackageManager#INTENT_FILTER_VERIFICATION_SUCCESS}
+ * or {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}.
+ * @param outFailedDomains a list of failed domains if the verificationCode is
+ * {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}, otherwise null;
+ * @throws SecurityException if the caller does not have the
+ * INTENT_FILTER_VERIFICATION_AGENT permission.
+ *
+ * @hide
+ */
+ public abstract void verifyIntentFilter(int verificationId, int verificationCode,
+ List<String> outFailedDomains);
+
+ /**
+ * Get the status of a Domain Verification Result for an IntentFilter. This is
+ * related to the {@link android.content.IntentFilter#setAutoVerify(boolean)} and
+ * {@link android.content.IntentFilter#getAutoVerify()}
+ *
+ * This is used by the ResolverActivity to change the status depending on what the User select
+ * in the Disambiguation Dialog and also used by the Settings App for changing the default App
+ * for a domain.
+ *
+ * @param packageName The package name of the Activity associated with the IntentFilter.
+ * @param userId The user id.
+ *
+ * @return The status to set to. This can be
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK} or
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS} or
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER} or
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED}
+ *
+ * @hide
+ */
+ public abstract int getIntentVerificationStatus(String packageName, int userId);
+
+ /**
+ * Allow to change the status of a Intent Verification status for all IntentFilter of an App.
+ * This is related to the {@link android.content.IntentFilter#setAutoVerify(boolean)} and
+ * {@link android.content.IntentFilter#getAutoVerify()}
+ *
+ * This is used by the ResolverActivity to change the status depending on what the User select
+ * in the Disambiguation Dialog and also used by the Settings App for changing the default App
+ * for a domain.
+ *
+ * @param packageName The package name of the Activity associated with the IntentFilter.
+ * @param status The status to set to. This can be
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK} or
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS} or
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER}
+ * @param userId The user id.
+ *
+ * @return true if the status has been set. False otherwise.
+ *
+ * @hide
+ */
+ public abstract boolean updateIntentVerificationStatus(String packageName, int status,
+ int userId);
+
+ /**
+ * Get the list of IntentFilterVerificationInfo for a specific package and User.
+ *
+ * @param packageName the package name. When this parameter is set to a non null value,
+ * the results will be filtered by the package name provided.
+ * Otherwise, there will be no filtering and it will return a list
+ * corresponding for all packages for the provided userId.
+ * @return a list of IntentFilterVerificationInfo for a specific package and User.
+ */
+ public abstract List<IntentFilterVerificationInfo> getIntentFilterVerifications(
+ String packageName);
+
+ /**
* Change the installer associated with a given package. There are limitations
* on how the installer package can be changed; in particular:
* <ul>
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 212cf6d..e20057d 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3149,7 +3149,7 @@ public class PackageParser {
if (parser.getName().equals("intent-filter")) {
ActivityIntentInfo intent = new ActivityIntentInfo(a);
- if (!parseIntent(res, parser, attrs, true, intent, outError)) {
+ if (!parseIntent(res, parser, attrs, true, true, intent, outError)) {
return null;
}
if (intent.countActions() == 0) {
@@ -3161,7 +3161,7 @@ public class PackageParser {
}
} else if (!receiver && parser.getName().equals("preferred")) {
ActivityIntentInfo intent = new ActivityIntentInfo(a);
- if (!parseIntent(res, parser, attrs, false, intent, outError)) {
+ if (!parseIntent(res, parser, attrs, false, false, intent, outError)) {
return null;
}
if (intent.countActions() == 0) {
@@ -3341,7 +3341,7 @@ public class PackageParser {
if (parser.getName().equals("intent-filter")) {
ActivityIntentInfo intent = new ActivityIntentInfo(a);
- if (!parseIntent(res, parser, attrs, true, intent, outError)) {
+ if (!parseIntent(res, parser, attrs, true, true, intent, outError)) {
return null;
}
if (intent.countActions() == 0) {
@@ -3521,7 +3521,7 @@ public class PackageParser {
if (parser.getName().equals("intent-filter")) {
ProviderIntentInfo intent = new ProviderIntentInfo(outInfo);
- if (!parseIntent(res, parser, attrs, true, intent, outError)) {
+ if (!parseIntent(res, parser, attrs, true, false, intent, outError)) {
return false;
}
outInfo.intents.add(intent);
@@ -3780,7 +3780,7 @@ public class PackageParser {
if (parser.getName().equals("intent-filter")) {
ServiceIntentInfo intent = new ServiceIntentInfo(s);
- if (!parseIntent(res, parser, attrs, true, intent, outError)) {
+ if (!parseIntent(res, parser, attrs, true, false, intent, outError)) {
return null;
}
@@ -3981,7 +3981,7 @@ public class PackageParser {
= "http://schemas.android.com/apk/res/android";
private boolean parseIntent(Resources res, XmlPullParser parser, AttributeSet attrs,
- boolean allowGlobs, IntentInfo outInfo, String[] outError)
+ boolean allowGlobs, boolean allowAutoVerify, IntentInfo outInfo, String[] outError)
throws XmlPullParserException, IOException {
TypedArray sa = res.obtainAttributes(attrs,
@@ -4006,6 +4006,12 @@ public class PackageParser {
outInfo.banner = sa.getResourceId(
com.android.internal.R.styleable.AndroidManifestIntentFilter_banner, 0);
+ if (allowAutoVerify) {
+ outInfo.setAutoVerify(sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestIntentFilter_autoVerify,
+ false));
+ }
+
sa.recycle();
int outerDepth = parser.getDepth();
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index a9c7be3..92b8055 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -37,10 +37,14 @@ public class PackageUserState {
public ArraySet<String> disabledComponents;
public ArraySet<String> enabledComponents;
+ public int domainVerificationStatus;
+
public PackageUserState() {
installed = true;
hidden = false;
enabled = COMPONENT_ENABLED_STATE_DEFAULT;
+ domainVerificationStatus =
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
}
public PackageUserState(PackageUserState o) {
@@ -51,9 +55,10 @@ public class PackageUserState {
hidden = o.hidden;
lastDisableAppCaller = o.lastDisableAppCaller;
disabledComponents = o.disabledComponents != null
- ? new ArraySet<String>(o.disabledComponents) : null;
+ ? new ArraySet<>(o.disabledComponents) : null;
enabledComponents = o.enabledComponents != null
- ? new ArraySet<String>(o.enabledComponents) : null;
+ ? new ArraySet<>(o.enabledComponents) : null;
blockUninstall = o.blockUninstall;
+ domainVerificationStatus = o.domainVerificationStatus;
}
}
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
index fe3aec9..7b141f0 100644
--- a/core/java/android/content/pm/ResolveInfo.java
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -143,6 +143,11 @@ public class ResolveInfo implements Parcelable {
*/
public boolean system;
+ /**
+ * @hide Does the associated IntentFilter needs verification ?
+ */
+ public boolean filterNeedsVerification;
+
private ComponentInfo getComponentInfo() {
if (activityInfo != null) return activityInfo;
if (serviceInfo != null) return serviceInfo;
@@ -283,6 +288,7 @@ public class ResolveInfo implements Parcelable {
resolvePackageName = orig.resolvePackageName;
system = orig.system;
targetUserId = orig.targetUserId;
+ filterNeedsVerification = orig.filterNeedsVerification;
}
public String toString() {
@@ -344,6 +350,7 @@ public class ResolveInfo implements Parcelable {
dest.writeInt(targetUserId);
dest.writeInt(system ? 1 : 0);
dest.writeInt(noResourceId ? 1 : 0);
+ dest.writeInt(filterNeedsVerification ? 1 : 0);
}
public static final Creator<ResolveInfo> CREATOR
@@ -389,6 +396,7 @@ public class ResolveInfo implements Parcelable {
targetUserId = source.readInt();
system = source.readInt() != 0;
noResourceId = source.readInt() != 0;
+ filterNeedsVerification = source.readInt() != 0;
}
public static class DisplayNameComparator
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index fb51528..8e5d245 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6097,7 +6097,7 @@ public final class Settings {
public static final String PACKAGE_VERIFIER_SETTING_VISIBLE = "verifier_setting_visible";
/**
- * Run package verificaiton on apps installed through ADB/ADT/USB
+ * Run package verification on apps installed through ADB/ADT/USB
* 1 = perform package verification on ADB installs (default)
* 0 = bypass package verification on ADB installs
* @hide
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 3ceea9d..6b35f3f 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -604,9 +604,10 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
if ((mAlwaysUseOption || mAdapter.hasFilteredItem()) && mAdapter.mOrigResolveList != null) {
// Build a reasonable intent filter, based on what matched.
IntentFilter filter = new IntentFilter();
+ String action = intent.getAction();
- if (intent.getAction() != null) {
- filter.addAction(intent.getAction());
+ if (action != null) {
+ filter.addAction(action);
}
Set<String> categories = intent.getCategories();
if (categories != null) {
@@ -688,8 +689,30 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
if (r.match > bestMatch) bestMatch = r.match;
}
if (alwaysCheck) {
- getPackageManager().addPreferredActivity(filter, bestMatch, set,
- intent.getComponent());
+ PackageManager pm = getPackageManager();
+
+ // Set the preferred Activity
+ pm.addPreferredActivity(filter, bestMatch, set, intent.getComponent());
+
+ // Update Domain Verification status
+ int userId = getUserId();
+ ComponentName cn = intent.getComponent();
+ String packageName = cn.getPackageName();
+ String dataScheme = (data != null) ? data.getScheme() : null;
+
+ boolean isHttpOrHttps = (dataScheme != null) &&
+ (dataScheme.equals(IntentFilter.SCHEME_HTTP) ||
+ dataScheme.equals(IntentFilter.SCHEME_HTTPS));
+
+ boolean isViewAction = (action != null) && action.equals(Intent.ACTION_VIEW);
+ boolean hasCategoryBrowsable = (categories != null) &&
+ categories.contains(Intent.CATEGORY_BROWSABLE);
+
+ if (isHttpOrHttps && isViewAction && hasCategoryBrowsable) {
+ pm.updateIntentVerificationStatus(packageName,
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS,
+ userId);
+ }
} else {
try {
AppGlobals.getPackageManager().setLastChosenActivity(intent,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 851c4bf..0b1b807 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -73,6 +73,7 @@
<protected-broadcast android:name="android.intent.action.USER_BACKGROUND" />
<protected-broadcast android:name="android.intent.action.USER_FOREGROUND" />
<protected-broadcast android:name="android.intent.action.USER_SWITCHED" />
+ <protected-broadcast android:name="android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGING" />
@@ -2799,6 +2800,23 @@
android:description="@string/permdesc_bindPackageVerifier"
android:protectionLevel="signature" />
+ <!-- @SystemApi @hide Intent filter verifier needs to have this permission before the
+ PackageManager will trust it to verify intent filters.
+ -->
+ <permission android:name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"
+ android:label="@string/permlab_intentFilterVerificationAgent"
+ android:description="@string/permdesc_intentFilterVerificationAgent"
+ android:protectionLevel="signature|system" />
+
+ <!-- Must be required by intent filter verifier receiver, to ensure that only the
+ system can interact with it.
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_INTENT_FILTER_VERIFIER"
+ android:label="@string/permlab_bindIntentFilterVerifier"
+ android:description="@string/permdesc_bindIntentFilterVerifier"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows applications to access serial ports via the SerialManager.
@hide -->
<permission android:name="android.permission.SERIAL_PORT"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 283c237..b0b4e3a 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1042,6 +1042,18 @@
libraries in the apk must be stored and page-aligned. -->
<attr name="extractNativeLibs" format="boolean"/>
+ <!-- Specify whether an activity intent filter will need to be verified thru its set
+ of data URIs. This will only be used when the Intent's action is set to
+ {@link android.content.Intent#ACTION_VIEW Intent.ACTION_VIEW} and the Intent's category is
+ set to {@link android.content.Intent#CATEGORY_BROWSABLE Intent.CATEGORY_BROWSABLE} and the
+ intern filter data scheme is set to "http" or "https". When set to true, the intent filter
+ will need to use its data tag for getting the URIs to verify with.
+
+ For each URI, an HTTPS network request will be done to <code>/.well-known/associations.json</code>
+ host to verify that the web site is okay with the app intercepting the URI.
+ -->
+ <attr name="autoVerify" format="boolean" />
+
<!-- The <code>manifest</code> tag is the root of an
<code>AndroidManifest.xml</code> file,
describing the contents of an Android package (.apk) file. One
@@ -1840,6 +1852,7 @@
<attr name="banner" />
<attr name="logo" />
<attr name="priority" />
+ <attr name="autoVerify" />
</declare-styleable>
<!-- Attributes that can be supplied in an AndroidManifest.xml
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index ef7bfaf..5c7daf2 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2653,4 +2653,8 @@
<public type="attr" name="extractNativeLibs" />
<public type="attr" name="usesCleartextTraffic" />
+
+ <!--IntentFilter auto verification -->
+ <public type="attr" name="autoVerify" />
+
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 88225bd..32a4ca7 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3367,6 +3367,22 @@
<string name="permdesc_bindPackageVerifier">Allows the holder to make requests of
package verifiers. Should never be needed for normal apps.</string>
+ <!-- Title of an application permission which allows the application to verify whether
+ a different intent filter is able to be verified by some internal logic. [CHAR LIMIT=40] -->
+ <string name="permlab_intentFilterVerificationAgent">verify intent filter</string>
+ <!-- Description of an application permission which allows the application to verify whether
+ a different intent filter is able to be verified by some internal heuristic. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_intentFilterVerificationAgent">Allows the app to check if an intent filter
+ is verified or not.</string>
+
+ <!-- Title of an application permission which allows the application to verify whether
+ a different intent filter is able to be verified by some internal logic. [CHAR LIMIT=40] -->
+ <string name="permlab_bindIntentFilterVerifier">bind to an intent filter verifier</string>
+ <!-- Description of an application permission which allows the application to verify whether
+ a different intent filter is able to be verified by some internal logic. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_bindIntentFilterVerifier">Allows the holder to make requests of
+ intent filter verifiers. Should never be needed for normal apps.</string>
+
<!-- Title of an application permission which allows the application to access serial ports via the SerialManager. [CHAR LIMIT=40] -->
<string name="permlab_serialPort">access serial ports</string>
<!-- Description of an application permission which allows the application access serial ports via the SerialManager. [CHAR LIMIT=NONE] -->
diff --git a/packages/IntentFilterVerifier/Android.mk b/packages/IntentFilterVerifier/Android.mk
new file mode 100644
index 0000000..99feda5
--- /dev/null
+++ b/packages/IntentFilterVerifier/Android.mk
@@ -0,0 +1,22 @@
+LOCAL_PATH:= $(call my-dir)
+
+# Build the IntentFilterVerifier.
+include $(CLEAR_VARS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ volley \
+
+LOCAL_JAVA_LIBRARIES += org.apache.http.legacy
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := IntentFilterVerifier
+
+LOCAL_PRIVILEGED_MODULE := true
+
+LOCAL_PROGUARD_FLAGS := $(proguard.flags)
+
+include $(BUILD_PACKAGE)
+
+# Build the test package.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/packages/IntentFilterVerifier/AndroidManifest.xml b/packages/IntentFilterVerifier/AndroidManifest.xml
new file mode 100644
index 0000000..3829cc5
--- /dev/null
+++ b/packages/IntentFilterVerifier/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.verifier.intentfilter"
+ coreApp="true">
+ <uses-permission android:name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/>
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+
+ <application
+ android:label="@string/service_name"
+ android:allowBackup="false">
+
+ <receiver
+ android:name="com.android.verifier.intentfilter.IntentVerificationReceiver"
+ android:permission="android.permission.BIND_INTENT_FILTER_VERIFIER" >
+ <intent-filter
+ android:priority="-1" >
+ <action android:name="android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION" />
+ <data android:mimeType="application/vnd.android.package-archive" />
+ </intent-filter>
+ </receiver>
+
+ <service android:name=".IntentVerificationService"
+ android:label="@string/service_name"
+ android:exported="false"/>
+
+ </application>
+
+</manifest>
diff --git a/packages/IntentFilterVerifier/CleanSpec.mk b/packages/IntentFilterVerifier/CleanSpec.mk
new file mode 100644
index 0000000..e4575ae
--- /dev/null
+++ b/packages/IntentFilterVerifier/CleanSpec.mk
@@ -0,0 +1,49 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list. These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list. E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# *****************************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THE BANNER
+# *****************************************************************
+
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+
+# ******************************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
+# ******************************************************************
diff --git a/packages/IntentFilterVerifier/proguard.flags b/packages/IntentFilterVerifier/proguard.flags
new file mode 100644
index 0000000..6e4bec3
--- /dev/null
+++ b/packages/IntentFilterVerifier/proguard.flags
@@ -0,0 +1 @@
+-verbose \ No newline at end of file
diff --git a/packages/IntentFilterVerifier/res/values/strings.xml b/packages/IntentFilterVerifier/res/values/strings.xml
new file mode 100644
index 0000000..22f3cd5
--- /dev/null
+++ b/packages/IntentFilterVerifier/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Package name shown to users when they look at installed applications
+ and running processes. This service verifies packages that are
+ requested to be installed. [CHAR LIMIT=50] -->
+ <string name="service_name">Basic Intent Filter Verification Service</string>
+
+</resources>
diff --git a/packages/IntentFilterVerifier/src/com/android/verifier/intentfilter/IntentVerificationReceiver.java b/packages/IntentFilterVerifier/src/com/android/verifier/intentfilter/IntentVerificationReceiver.java
new file mode 100644
index 0000000..de25f8c
--- /dev/null
+++ b/packages/IntentFilterVerifier/src/com/android/verifier/intentfilter/IntentVerificationReceiver.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.verifier.intentfilter;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class IntentVerificationReceiver extends BroadcastReceiver {
+ static final String TAG = IntentVerificationReceiver.class.getName();
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION.equals(action)) {
+ Bundle extras = intent.getExtras();
+ if (extras != null) {
+ int verificationId = extras.getInt(
+ PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID);
+ String hosts = extras.getString(
+ PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS);
+
+ Log.d(TAG, "Received IntentFilter verification broadcast with verificationId: "
+ + verificationId);
+
+ if (canDoVerification(context)) {
+ Intent serviceIntent = new Intent(context, IntentVerificationService.class);
+ serviceIntent.fillIn(intent, 0);
+ serviceIntent.putExtras(intent.getExtras());
+
+ Slog.d(TAG, "Starting Intent Verification Service.");
+
+ context.startService(serviceIntent);
+ } else {
+ sendVerificationFailure(context, verificationId, hosts);
+ }
+ }
+
+ } else {
+ Log.w(TAG, "Unexpected action: " + action);
+ }
+ }
+
+ private void sendVerificationFailure(Context context, int verificationId, String hosts) {
+ List<String> list = Arrays.asList(hosts.split(" "));
+ context.getPackageManager().verifyIntentFilter(
+ verificationId, PackageManager.INTENT_FILTER_VERIFICATION_FAILURE, list);
+
+ Log.d(TAG, "No network! Failing IntentFilter verification with verificationId: " +
+ verificationId + " and hosts: " + hosts);
+ }
+
+ private boolean canDoVerification(Context context) {
+ return hasNetwork(context);
+ }
+
+ public boolean hasNetwork(Context context) {
+ ConnectivityManager cm =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (cm != null) {
+ NetworkInfo info = cm.getActiveNetworkInfo();
+ return (info != null) && info.isConnected();
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/packages/IntentFilterVerifier/src/com/android/verifier/intentfilter/IntentVerificationRequest.java b/packages/IntentFilterVerifier/src/com/android/verifier/intentfilter/IntentVerificationRequest.java
new file mode 100644
index 0000000..8f9c86f
--- /dev/null
+++ b/packages/IntentFilterVerifier/src/com/android/verifier/intentfilter/IntentVerificationRequest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.verifier.intentfilter;
+
+import com.android.volley.Response;
+import com.android.volley.toolbox.JsonArrayRequest;
+import org.json.JSONArray;
+
+public class IntentVerificationRequest extends JsonArrayRequest {
+
+ public IntentVerificationRequest(String url, Response.Listener<JSONArray> listener,
+ Response.ErrorListener errorListener) {
+ super(url, listener, errorListener);
+ }
+}
diff --git a/packages/IntentFilterVerifier/src/com/android/verifier/intentfilter/IntentVerificationService.java b/packages/IntentFilterVerifier/src/com/android/verifier/intentfilter/IntentVerificationService.java
new file mode 100644
index 0000000..3e4db6c
--- /dev/null
+++ b/packages/IntentFilterVerifier/src/com/android/verifier/intentfilter/IntentVerificationService.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.verifier.intentfilter;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import com.android.volley.RequestQueue;
+import com.android.volley.Response;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.Volley;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+public class IntentVerificationService extends Service {
+ private static final String TAG = "IntentVerificationService";
+
+ private static final String WELL_KNOWN_ASSOCIATIONS_JSON = "/.well-known/associations.json";
+ private static final String DEFAULT_SCHEME = "https";
+
+ private static final String JSON_KEY_TARGET = "target";
+ private static final String JSON_KEY_NAMESPACE = "namespace";
+ private static final String JSON_KEY_PACKAGE_NAME = "package_name";
+ private static final String JSON_KEY_CERT_FINGERPRINTS = "sha256_cert_fingerprints";
+
+ private static final String JSON_VAL_ANDROID_APP = "android_app";
+
+ private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'A', 'B', 'C', 'D', 'E', 'F' };
+
+ private ConnectivityManager mConnectivityManager;
+ private Looper mHandlerLooper;
+ private VerificationHandler mHandler;
+ private RequestQueue mRequestQueue;
+
+ private static class VerificationState {
+ public final int verificationId;
+ public final String hosts;
+ public final String packageName;
+ public final Set<String> fingerprints;
+ public int responseCode = PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS;
+ public int counter;
+ public int numberOfHosts;
+ public ArrayList<String> failedHosts = new ArrayList<>();
+
+ private final Object lock = new Object();
+
+ public VerificationState(int id, String h, String p, Set<String> fps) {
+ verificationId = id;
+ hosts = h;
+ packageName = p;
+ fingerprints = fps;
+ numberOfHosts = hosts.split(" ").length;
+ }
+ public boolean setResponseCodeAndCheckMax(int code) {
+ synchronized (lock) {
+ if (code == PackageManager.INTENT_FILTER_VERIFICATION_FAILURE) {
+ responseCode = code;
+ counter++;
+ } else if (code == PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS) {
+ counter++;
+ }
+ return (counter == numberOfHosts);
+ }
+ }
+
+ public void addFailedHost(String host) {
+ synchronized (failedHosts) {
+ failedHosts.add(host);
+ }
+ }
+
+ public ArrayList<String> getFailedHosts() {
+ return failedHosts;
+ }
+ }
+
+ private HashMap<Integer, VerificationState> mVerificationMap =
+ new HashMap<Integer, VerificationState>();
+
+ private class VerificationHandler extends Handler {
+ private static final int MSG_STOP_SERVICE = 0;
+ private static final int MSG_VERIFY_INTENT_START = 1;
+ private static final int MSG_VERIFY_INTENT_DONE = 2;
+
+ private static final long SHUTDOWN_DELAY_MILLIS = 8 * 1000;
+
+ private final Context mContext;
+
+ public VerificationHandler(Context context, Looper looper) {
+ super(looper);
+
+ mContext = context;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_VERIFY_INTENT_START:
+ final Intent intent = (Intent) msg.obj;
+ Bundle extras = intent.getExtras();
+ boolean immediate = false;
+
+ if (extras != null) {
+ immediate = doVerification(extras);
+ }
+
+ // There was no network, so we can stop soon
+ if (immediate) {
+ stopDelayed();
+ }
+ break;
+
+ case MSG_VERIFY_INTENT_DONE:
+ VerificationState vs = (VerificationState) msg.obj;
+ processVerificationDone(mContext, vs);
+ clearVerificationState(vs);
+ break;
+
+ case MSG_STOP_SERVICE:
+ stopSelf();
+ break;
+
+ default:
+ Slog.i(TAG, "Unknown message posted " + msg.toString());
+ break;
+
+ }
+ }
+
+ private void stopDelayed() {
+ removeMessages(MSG_STOP_SERVICE);
+ sendEmptyMessageDelayed(MSG_STOP_SERVICE, SHUTDOWN_DELAY_MILLIS);
+ }
+ }
+
+ private VerificationState getVerificationState(int id, String hosts, String packageName,
+ Set<String> fingerprints) {
+ synchronized (mVerificationMap) {
+ VerificationState vs = mVerificationMap.get(id);
+ if (vs == null) {
+ vs = new VerificationState(id, hosts, packageName, fingerprints);
+ }
+ return vs;
+ }
+ }
+
+ private void clearVerificationState(VerificationState vs) {
+ mVerificationMap.remove(vs);
+ }
+
+ private boolean doVerification(Bundle extras) {
+ String scheme = extras.getString(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME);
+ if (TextUtils.isEmpty(scheme)) {
+ scheme = DEFAULT_SCHEME;
+ }
+
+ int verificationId = extras.getInt(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID);
+ String hosts = extras.getString(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS);
+ String packageName = extras.getString(
+ PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME);
+
+ Set<String> fingerprints = getFingerprints(packageName);
+
+ Log.d(TAG, "Received IntentFilter verification broadcast with verificationId:" +
+ verificationId + " hosts:'" + hosts + "' scheme:" + scheme);
+
+ VerificationState vs = getVerificationState(verificationId, hosts, packageName,
+ fingerprints);
+
+ if (hasNetwork()) {
+ sendNetworkVerifications(scheme, vs);
+ return false;
+ }
+
+ // No network, so fail immediately
+ sendFailureResponseIfNeeded(vs);
+
+ return true;
+ }
+
+ private Set<String> getFingerprints(String packageName) {
+ Context context = getApplicationContext();
+ try {
+ Signature[] signatures = context.getPackageManager().getPackageInfo(packageName,
+ PackageManager.GET_SIGNATURES).signatures;
+ if (signatures.length > 0) {
+ HashSet<String> result = new HashSet<String>();
+ for (Signature sig : signatures) {
+ String fingerprint = computeNormalizedSha256Fingerprint(sig.toByteArray());
+ result.add(fingerprint);
+ }
+ return result;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Cannot get signatures for package name: " + packageName);
+ }
+ return Collections.EMPTY_SET;
+ }
+
+ private static String computeNormalizedSha256Fingerprint(byte[] signature) {
+ MessageDigest digester;
+ try {
+ digester = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError("No SHA-256 implementation found.");
+ }
+ digester.update(signature);
+ return byteArrayToHexString(digester.digest());
+ }
+
+ private static String byteArrayToHexString(byte[] array) {
+ if (array.length == 0) {
+ return "";
+ }
+ char[] buf = new char[array.length * 3 - 1];
+
+ int bufIndex = 0;
+ for (int i = 0; i < array.length; i++) {
+ byte b = array[i];
+ if (i > 0) {
+ buf[bufIndex++] = ':';
+ }
+ buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
+ buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
+ }
+ return new String(buf);
+ }
+
+ private static String getAssociationPath() {
+ return WELL_KNOWN_ASSOCIATIONS_JSON;
+ }
+
+ private void sendNetworkVerifications(String scheme, final VerificationState vs) {
+ final int verificationId = vs.verificationId;
+ final String hosts = vs.hosts;
+
+ String[] array = hosts.split(" ");
+ for (final String host : array) {
+ try {
+ final URL url = new URL(scheme, host, getAssociationPath());
+ final String urlStr = url.toString();
+ Log.d(TAG, "Using verification URL: " + urlStr);
+ IntentVerificationRequest req = new IntentVerificationRequest(urlStr,
+ new Response.Listener<JSONArray>() {
+ @Override
+ public void onResponse(JSONArray response) {
+ Log.d(TAG, "From: " + urlStr + " received response: "
+ + response.toString());
+ handleResponse(vs, host, response);
+ }
+ }, new Response.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ Slog.d(TAG, "From: " + urlStr + " got error: " + error.getMessage()
+ + (error.networkResponse != null ? " with status code: "
+ + error.networkResponse.statusCode : ""));
+ handleError(vs, host);
+ }
+ }
+ );
+ mRequestQueue.add(req);
+ } catch (MalformedURLException e) {
+ Log.w(TAG, "Cannot send verificationId: " + verificationId + " to host: " + host);
+ }
+ }
+ }
+
+ private void handleError(VerificationState vs, String host) {
+ vs.addFailedHost(host);
+ sendFailureResponseIfNeeded(vs);
+ }
+
+ private void handleResponse(VerificationState vs, String host, JSONArray response) {
+ try {
+ if (response.length() == 0) {
+ Log.d(TAG, "Domain response is empty!");
+ handleError(vs, host);
+ return;
+ }
+
+ JSONObject firstRelation = (JSONObject) response.get(0);
+ if (firstRelation == null) {
+ Log.d(TAG, "Domain response is should have a relation!");
+ handleError(vs, host);
+ return;
+ }
+
+ JSONObject target = (JSONObject) firstRelation.get(JSON_KEY_TARGET);
+ if (target == null) {
+ Log.d(TAG, "Domain response target is empty!");
+ handleError(vs, host);
+ return;
+ }
+
+ String nameSpace = target.getString(JSON_KEY_NAMESPACE);
+ if (TextUtils.isEmpty(nameSpace) || !nameSpace.equals(JSON_VAL_ANDROID_APP)) {
+ Log.d(TAG, "Domain response target name space is not valid: " + nameSpace);
+ handleError(vs, host);
+ return;
+ }
+
+ String packageName = target.getString(JSON_KEY_PACKAGE_NAME);
+ JSONArray certFingerprints = target.getJSONArray(JSON_KEY_CERT_FINGERPRINTS);
+
+ // Early exits is the JSON response is not correct for the package name or signature
+ if (TextUtils.isEmpty(packageName)) {
+ Log.d(TAG, "Domain response has empty package name!");
+ handleError(vs, host);
+ return;
+ }
+ if (certFingerprints.length() == 0) {
+ Log.d(TAG, "Domain response has empty cert signature!");
+ handleError(vs, host);
+ return;
+ }
+ // Now do the real test on package name and signature
+ if (!packageName.equalsIgnoreCase(vs.packageName)) {
+ Log.d(TAG, "Domain response has package name mismatch!" + packageName +
+ " vs " + vs.packageName);
+ handleError(vs, host);
+ return;
+ }
+ final int count = certFingerprints.length();
+ for (int i = 0; i < count; i++) {
+ String fingerprint = certFingerprints.getString(i);
+ if (!vs.fingerprints.contains(fingerprint)) {
+ Log.d(TAG, "Domain response has cert fingerprint mismatch! " +
+ "The domain fingerprint '" + fingerprint + "' is not from the App");
+ handleError(vs, host);
+ return;
+ }
+ }
+ sendSuccessResponseIfNeeded(vs);
+ } catch (JSONException e) {
+ Log.d(TAG, "Domain response is not well formed", e);
+ handleError(vs, host);
+ }
+ }
+
+ private void sendSuccessResponseIfNeeded(VerificationState vs) {
+ if (vs.setResponseCodeAndCheckMax(PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS)) {
+ sendMessage(vs);
+ }
+ }
+
+ private void sendFailureResponseIfNeeded(VerificationState vs) {
+ if (vs.setResponseCodeAndCheckMax(PackageManager.INTENT_FILTER_VERIFICATION_FAILURE)) {
+ sendMessage(vs);
+ }
+ }
+
+ private void sendMessage(VerificationState vs) {
+ Message msg = mHandler.obtainMessage(VerificationHandler.MSG_VERIFY_INTENT_DONE);
+ msg.obj = vs;
+ mHandler.sendMessage(msg);
+ }
+
+ private void processVerificationDone(Context context, VerificationState state) {
+ int verificationId = state.verificationId;
+ String hosts = state.hosts;
+ int responseCode = state.responseCode;
+
+ final PackageManager pm = context.getPackageManager();
+
+ // Callback the PackageManager
+ pm.verifyIntentFilter(verificationId, responseCode, state.getFailedHosts());
+ Log.d(TAG, "IntentFilter with verificationId: " + verificationId + " and hosts: " +
+ hosts + " got verification code: " + responseCode);
+ }
+
+ /**
+ * We only connect to this service from the same process.
+ */
+ public class LocalBinder extends Binder {
+ IntentVerificationService getService() { return IntentVerificationService.this; }
+ }
+
+ private final IBinder mBinder = new LocalBinder();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Slog.i(TAG, "Received start id " + startId + ": " + intent);
+
+ final Message msg = mHandler.obtainMessage(VerificationHandler.MSG_VERIFY_INTENT_START);
+ msg.obj = intent;
+ mHandler.sendMessage(msg);
+
+ return START_STICKY;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ Slog.d(TAG, "Starting up...");
+
+ final HandlerThread handlerThread = new HandlerThread("IntentVerificationService");
+ handlerThread.start();
+ mHandlerLooper = handlerThread.getLooper();
+
+ mHandler = new VerificationHandler(getApplicationContext(), mHandlerLooper);
+
+ mRequestQueue = Volley.newRequestQueue(this);
+ mRequestQueue.start();
+
+ mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ Slog.d(TAG, "Shutting down...");
+
+ mHandlerLooper.quit();
+ mRequestQueue.stop();
+ }
+
+ private boolean hasNetwork() {
+ NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
+ return (info != null) && info.isConnected();
+ }
+}
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index cea1ebe..744156b 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -47,6 +47,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
final private static String TAG = "IntentResolver";
final private static boolean DEBUG = false;
final private static boolean localLOGV = DEBUG || false;
+ final private static boolean localVerificationLOGV = DEBUG || false;
public void addFilter(F f) {
if (localLOGV) {
@@ -478,7 +479,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
/**
* Returns whether the object associated with the given filter is
- * "stopped," that is whether it should not be included in the result
+ * "stopped", that is whether it should not be included in the result
* if the intent requests to excluded stopped objects.
*/
protected boolean isFilterStopped(F filter, int userId) {
@@ -486,6 +487,22 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
}
/**
+ * Returns whether the given filter is "verified" that is whether it has been verified against
+ * its data URIs.
+ *
+ * The verification would happen only and only if the Intent action is
+ * {@link android.content.Intent#ACTION_VIEW} and the Intent category is
+ * {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent data scheme
+ * is "http" or "https".
+ *
+ * @see android.content.IntentFilter#setAutoVerify(boolean)
+ * @see android.content.IntentFilter#getAutoVerify()
+ */
+ protected boolean isFilterVerified(F filter) {
+ return filter.isVerified();
+ }
+
+ /**
* Returns whether this filter is owned by this package. This must be
* implemented to provide correct filtering of Intents that have
* specified a package name they are to be delivered to.
@@ -710,6 +727,13 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
continue;
}
+ // Are we verified ?
+ if (filter.getAutoVerify()) {
+ if (localVerificationLOGV || debug) {
+ Slog.v(TAG, " Filter verified: " + isFilterVerified(filter));
+ }
+ }
+
// Do we already have this one?
if (!allowFilterResult(filter, dest)) {
if (debug) {
diff --git a/services/core/java/com/android/server/pm/IntentFilterVerificationKey.java b/services/core/java/com/android/server/pm/IntentFilterVerificationKey.java
new file mode 100644
index 0000000..399b03c
--- /dev/null
+++ b/services/core/java/com/android/server/pm/IntentFilterVerificationKey.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import java.util.Arrays;
+
+/**
+ * This is the key for the map of {@link android.content.pm.IntentFilterVerificationInfo}s
+ * maintained by the {@link com.android.server.pm.PackageManagerService}
+ */
+class IntentFilterVerificationKey {
+ public String domains;
+ public String packageName;
+ public String className;
+
+ public IntentFilterVerificationKey(String[] domains, String packageName, String className) {
+ StringBuilder sb = new StringBuilder();
+ for (String host : domains) {
+ sb.append(host);
+ }
+ this.domains = sb.toString();
+ this.packageName = packageName;
+ this.className = className;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ IntentFilterVerificationKey that = (IntentFilterVerificationKey) o;
+
+ if (domains != null ? !domains.equals(that.domains) : that.domains != null) return false;
+ if (className != null ? !className.equals(that.className) : that.className != null)
+ return false;
+ if (packageName != null ? !packageName.equals(that.packageName) : that.packageName != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = domains != null ? domains.hashCode() : 0;
+ result = 31 * result + (packageName != null ? packageName.hashCode() : 0);
+ result = 31 * result + (className != null ? className.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/IntentFilterVerificationResponse.java b/services/core/java/com/android/server/pm/IntentFilterVerificationResponse.java
new file mode 100644
index 0000000..ead399b
--- /dev/null
+++ b/services/core/java/com/android/server/pm/IntentFilterVerificationResponse.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+
+import java.util.List;
+
+/* package private */ class IntentFilterVerificationResponse {
+ public final int callerUid;
+ public final int code;
+ public final List<String> failedDomains;
+
+ public IntentFilterVerificationResponse(int callerUid, int code, List<String> failedDomains) {
+ this.callerUid = callerUid;
+ this.code = code;
+ this.failedDomains = failedDomains;
+ }
+
+ public String getFailedDomainsString() {
+ StringBuilder sb = new StringBuilder();
+ for (String domain : failedDomains) {
+ if (sb.length() > 0) {
+ sb.append(" ");
+ }
+ sb.append(domain);
+ }
+ return sb.toString();
+ }
+}
diff --git a/services/core/java/com/android/server/pm/IntentFilterVerificationState.java b/services/core/java/com/android/server/pm/IntentFilterVerificationState.java
new file mode 100644
index 0000000..c09d6ae
--- /dev/null
+++ b/services/core/java/com/android/server/pm/IntentFilterVerificationState.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+public class IntentFilterVerificationState {
+ static final String TAG = IntentFilterVerificationState.class.getName();
+
+ public final static int STATE_UNDEFINED = 0;
+ public final static int STATE_VERIFICATION_PENDING = 1;
+ public final static int STATE_VERIFICATION_SUCCESS = 2;
+ public final static int STATE_VERIFICATION_FAILURE = 3;
+
+ private int mRequiredVerifierUid = 0;
+
+ private int mState;
+
+ private ArrayList<PackageParser.ActivityIntentInfo> mFilters = new ArrayList<>();
+ private ArraySet<String> mHosts = new ArraySet<>();
+ private int mUserId;
+
+ private String mPackageName;
+ private boolean mVerificationComplete;
+
+ public IntentFilterVerificationState(int verifierUid, int userId, String packageName) {
+ mRequiredVerifierUid = verifierUid;
+ mUserId = userId;
+ mPackageName = packageName;
+ mState = STATE_UNDEFINED;
+ mVerificationComplete = false;
+ }
+
+ public void setState(int state) {
+ if (state > STATE_VERIFICATION_FAILURE || state < STATE_UNDEFINED) {
+ mState = STATE_UNDEFINED;
+ } else {
+ mState = state;
+ }
+ }
+
+ public int getState() {
+ return mState;
+ }
+
+ public void setPendingState() {
+ setState(STATE_VERIFICATION_PENDING);
+ }
+
+ public ArrayList<PackageParser.ActivityIntentInfo> getFilters() {
+ return mFilters;
+ }
+
+ public boolean isVerificationComplete() {
+ return mVerificationComplete;
+ }
+
+ public boolean isVerified() {
+ if (mVerificationComplete) {
+ return (mState == STATE_VERIFICATION_SUCCESS);
+ }
+ return false;
+ }
+
+ public int getUserId() {
+ return mUserId;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public String getHostsString() {
+ StringBuilder sb = new StringBuilder();
+ final int count = mHosts.size();
+ for (int i=0; i<count; i++) {
+ if (i > 0) {
+ sb.append(" ");
+ }
+ sb.append(mHosts.valueAt(i));
+ }
+ return sb.toString();
+ }
+
+ public boolean setVerifierResponse(int callerUid, int code) {
+ if (mRequiredVerifierUid == callerUid) {
+ int state = STATE_UNDEFINED;
+ if (code == PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS) {
+ state = STATE_VERIFICATION_SUCCESS;
+ } else if (code == PackageManager.INTENT_FILTER_VERIFICATION_FAILURE) {
+ state = STATE_VERIFICATION_FAILURE;
+ }
+ mVerificationComplete = true;
+ setState(state);
+ return true;
+ }
+ Log.d(TAG, "Cannot set verifier response with callerUid:" + callerUid + " and code:" +
+ code + " as required verifierUid is:" + mRequiredVerifierUid);
+ return false;
+ }
+
+ public void addFilter(PackageParser.ActivityIntentInfo filter) {
+ mFilters.add(filter);
+ mHosts.addAll(filter.getHostsList());
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c7dc74f..b9dfc21 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -44,6 +44,10 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_USER_RESTRICTED;
import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
import static android.content.pm.PackageManager.INSTALL_FORWARD_LOCK;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
import static android.content.pm.PackageParser.isApkFile;
import static android.os.Process.PACKAGE_INFO_GID;
import static android.os.Process.SYSTEM_UID;
@@ -59,6 +63,8 @@ import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
+import android.Manifest;
+import android.content.pm.IntentFilterVerificationInfo;
import android.util.ArrayMap;
import com.android.internal.R;
@@ -504,6 +510,231 @@ public class PackageManagerService extends IPackageManager.Stub {
boolean mResolverReplaced = false;
+ private final ComponentName mIntentFilterVerifierComponent;
+ private int mIntentFilterVerificationToken = 0;
+
+ final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates
+ = new SparseArray<IntentFilterVerificationState>();
+
+ private interface IntentFilterVerifier<T extends IntentFilter> {
+ boolean addOneIntentFilterVerification(int verifierId, int userId, int verificationId,
+ T filter, String packageName);
+ void startVerifications(int userId);
+ void receiveVerificationResponse(int verificationId);
+ }
+
+ private class IntentVerifierProxy implements IntentFilterVerifier<ActivityIntentInfo> {
+ private Context mContext;
+ private ComponentName mIntentFilterVerifierComponent;
+ private ArrayList<Integer> mCurrentIntentFilterVerifications = new ArrayList<Integer>();
+
+ public IntentVerifierProxy(Context context, ComponentName verifierComponent) {
+ mContext = context;
+ mIntentFilterVerifierComponent = verifierComponent;
+ }
+
+ private String getDefaultScheme() {
+ // TODO: replace SCHEME_HTTP with SCHEME_HTTPS
+ return IntentFilter.SCHEME_HTTP;
+ }
+
+ @Override
+ public void startVerifications(int userId) {
+ // Launch verifications requests
+ int count = mCurrentIntentFilterVerifications.size();
+ for (int n=0; n<count; n++) {
+ int verificationId = mCurrentIntentFilterVerifications.get(n);
+ final IntentFilterVerificationState ivs =
+ mIntentFilterVerificationStates.get(verificationId);
+
+ String packageName = ivs.getPackageName();
+ boolean modified = false;
+
+ ArrayList<PackageParser.ActivityIntentInfo> filters = ivs.getFilters();
+ final int filterCount = filters.size();
+ for (int m=0; m<filterCount; m++) {
+ PackageParser.ActivityIntentInfo filter = filters.get(m);
+ synchronized (mPackages) {
+ modified = mSettings.createIntentFilterVerificationIfNeededLPw(
+ packageName, filter.getHosts());
+ }
+ }
+ synchronized (mPackages) {
+ if (modified) {
+ scheduleWriteSettingsLocked();
+ }
+ }
+ sendVerificationRequest(userId, verificationId, ivs);
+ }
+ mCurrentIntentFilterVerifications.clear();
+ }
+
+ private void sendVerificationRequest(int userId, int verificationId,
+ IntentFilterVerificationState ivs) {
+
+ Intent verificationIntent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION);
+ verificationIntent.putExtra(
+ PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID,
+ verificationId);
+ verificationIntent.putExtra(
+ PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME,
+ getDefaultScheme());
+ verificationIntent.putExtra(
+ PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS,
+ ivs.getHostsString());
+ verificationIntent.putExtra(
+ PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME,
+ ivs.getPackageName());
+ verificationIntent.setComponent(mIntentFilterVerifierComponent);
+ verificationIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+ UserHandle user = new UserHandle(userId);
+ mContext.sendBroadcastAsUser(verificationIntent, user);
+ Slog.d(TAG, "Sending IntenFilter verification broadcast");
+ }
+
+ public void receiveVerificationResponse(int verificationId) {
+ IntentFilterVerificationState ivs = mIntentFilterVerificationStates.get(verificationId);
+
+ final boolean verified = ivs.isVerified();
+
+ ArrayList<PackageParser.ActivityIntentInfo> filters = ivs.getFilters();
+ final int count = filters.size();
+ for (int n=0; n<count; n++) {
+ PackageParser.ActivityIntentInfo filter = filters.get(n);
+ filter.setVerified(verified);
+
+ Slog.d(TAG, "IntentFilter " + filter.toString() + " verified with result:"
+ + verified + " and hosts:" + ivs.getHostsString());
+ }
+
+ mIntentFilterVerificationStates.remove(verificationId);
+
+ final String packageName = ivs.getPackageName();
+ IntentFilterVerificationInfo ivi = null;
+
+ synchronized (mPackages) {
+ ivi = mSettings.getIntentFilterVerificationLPr(packageName);
+ }
+ if (ivi == null) {
+ Slog.w(TAG, "IntentFilterVerificationInfo not found for verificationId:"
+ + verificationId + " packageName:" + packageName);
+ return;
+ }
+ Slog.d(TAG, "Updating IntentFilterVerificationInfo for verificationId: "
+ + verificationId);
+
+ synchronized (mPackages) {
+ if (verified) {
+ ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS);
+ } else {
+ ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK);
+ }
+ scheduleWriteSettingsLocked();
+
+ final int userId = ivs.getUserId();
+ if (userId != UserHandle.USER_ALL) {
+ final int userStatus =
+ mSettings.getIntentFilterVerificationStatusLPr(packageName, userId);
+
+ int updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ boolean needUpdate = false;
+
+ // We cannot override the STATUS_ALWAYS / STATUS_NEVER states if they have
+ // already been set by the User thru the Disambiguation dialog
+ switch (userStatus) {
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
+ if (verified) {
+ updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+ } else {
+ updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
+ }
+ needUpdate = true;
+ break;
+
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
+ if (verified) {
+ updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+ needUpdate = true;
+ }
+ break;
+
+ default:
+ // Nothing to do
+ }
+
+ if (needUpdate) {
+ mSettings.updateIntentFilterVerificationStatusLPw(
+ packageName, updatedStatus, userId);
+ scheduleWritePackageRestrictionsLocked(userId);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean addOneIntentFilterVerification(int verifierId, int userId, int verificationId,
+ ActivityIntentInfo filter, String packageName) {
+ if (!(filter.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
+ filter.hasDataScheme(IntentFilter.SCHEME_HTTPS))) {
+ Slog.d(TAG, "IntentFilter does not contain HTTP nor HTTPS data scheme");
+ return false;
+ }
+ IntentFilterVerificationState ivs = mIntentFilterVerificationStates.get(verificationId);
+ if (ivs == null) {
+ ivs = createDomainVerificationState(verifierId, userId, verificationId,
+ packageName);
+ }
+ ArrayList<String> hosts = filter.getHostsList();
+ if (!hasValidHosts(hosts)) {
+ return false;
+ }
+ ivs.addFilter(filter);
+ return true;
+ }
+
+ private IntentFilterVerificationState createDomainVerificationState(int verifierId,
+ int userId, int verificationId, String packageName) {
+ IntentFilterVerificationState ivs = new IntentFilterVerificationState(
+ verifierId, userId, packageName);
+ ivs.setPendingState();
+ synchronized (mPackages) {
+ mIntentFilterVerificationStates.append(verificationId, ivs);
+ mCurrentIntentFilterVerifications.add(verificationId);
+ }
+ return ivs;
+ }
+
+ private boolean hasValidHosts(ArrayList<String> hosts) {
+ if (hosts.size() == 0) {
+ Slog.d(TAG, "IntentFilter does not contain any data hosts");
+ return false;
+ }
+ String hostEndBase = null;
+ for (String host : hosts) {
+ String[] hostParts = host.split("\\.");
+ // Should be at minimum a host like "example.com"
+ if (hostParts.length < 2) {
+ Slog.d(TAG, "IntentFilter does not contain a valid data host name: " + host);
+ return false;
+ }
+ // Verify that we have the same ending domain
+ int length = hostParts.length;
+ String hostEnd = hostParts[length - 1] + hostParts[length - 2];
+ if (hostEndBase == null) {
+ hostEndBase = hostEnd;
+ }
+ if (!hostEnd.equalsIgnoreCase(hostEndBase)) {
+ Slog.d(TAG, "IntentFilter does not contain the same data domains");
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private IntentFilterVerifier mIntentFilterVerifier;
+
// Set of pending broadcasts for aggregating enable/disable of components.
static class PendingPackageBroadcasts {
// for each user id, a map of <package name -> components within that package>
@@ -590,6 +821,8 @@ public class PackageManagerService extends IPackageManager.Stub {
static final int WRITE_PACKAGE_RESTRICTIONS = 14;
static final int PACKAGE_VERIFIED = 15;
static final int CHECK_PENDING_VERIFICATION = 16;
+ static final int START_INTENT_FILTER_VERIFICATIONS = 17;
+ static final int INTENT_FILTER_VERIFIED = 18;
static final int WRITE_SETTINGS_DELAY = 10*1000; // 10 seconds
@@ -1240,6 +1473,54 @@ public class PackageManagerService extends IPackageManager.Stub {
break;
}
+ case START_INTENT_FILTER_VERIFICATIONS: {
+ int userId = msg.arg1;
+ int verifierUid = msg.arg2;
+ PackageParser.Package pkg = (PackageParser.Package)msg.obj;
+
+ verifyIntentFiltersIfNeeded(userId, verifierUid, pkg);
+ break;
+ }
+ case INTENT_FILTER_VERIFIED: {
+ final int verificationId = msg.arg1;
+
+ final IntentFilterVerificationState state = mIntentFilterVerificationStates.get(
+ verificationId);
+ if (state == null) {
+ Slog.w(TAG, "Invalid IntentFilter verification token "
+ + verificationId + " received");
+ break;
+ }
+
+ final int userId = state.getUserId();
+
+ Slog.d(TAG, "Processing IntentFilter verification with token:"
+ + verificationId + " and userId:" + userId);
+
+ final IntentFilterVerificationResponse response =
+ (IntentFilterVerificationResponse) msg.obj;
+
+ state.setVerifierResponse(response.callerUid, response.code);
+
+ Slog.d(TAG, "IntentFilter verification with token:" + verificationId
+ + " and userId:" + userId
+ + " is settings verifier response with response code:"
+ + response.code);
+
+ if (response.code == PackageManager.INTENT_FILTER_VERIFICATION_FAILURE) {
+ Slog.d(TAG, "Domains failing verification: "
+ + response.getFailedDomainsString());
+ }
+
+ if (state.isVerificationComplete()) {
+ mIntentFilterVerifier.receiveVerificationResponse(verificationId);
+ } else {
+ Slog.d(TAG, "IntentFilter verification with token:" + verificationId
+ + " was not said to be complete");
+ }
+
+ break;
+ }
}
}
}
@@ -1851,11 +2132,16 @@ public class PackageManagerService extends IPackageManager.Stub {
SystemClock.uptimeMillis());
mRequiredVerifierPackage = getRequiredVerifierLPr();
+
+ mInstallerService = new PackageInstallerService(context, this, mAppInstallDir);
+
+ mIntentFilterVerifierComponent = getIntentFilterVerifierComponentNameLPr();
+ mIntentFilterVerifier = new IntentVerifierProxy(mContext,
+ mIntentFilterVerifierComponent);
+
} // synchronized (mPackages)
} // synchronized (mInstallLock)
- mInstallerService = new PackageInstallerService(context, this, mAppInstallDir);
-
// Now after opening every single application zip, make sure they
// are all flushed. Not really needed, but keeps things nice and
// tidy.
@@ -1909,6 +2195,46 @@ public class PackageManagerService extends IPackageManager.Stub {
return requiredVerifier;
}
+ private ComponentName getIntentFilterVerifierComponentNameLPr() {
+ final Intent verification = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION);
+ final List<ResolveInfo> receivers = queryIntentReceivers(verification, PACKAGE_MIME_TYPE,
+ PackageManager.GET_DISABLED_COMPONENTS, 0 /* userId */);
+
+ ComponentName verifierComponentName = null;
+
+ int priority = -1000;
+ final int N = receivers.size();
+ for (int i = 0; i < N; i++) {
+ final ResolveInfo info = receivers.get(i);
+
+ if (info.activityInfo == null) {
+ continue;
+ }
+
+ final String packageName = info.activityInfo.packageName;
+
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps == null) {
+ continue;
+ }
+
+ if (checkPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT,
+ packageName, UserHandle.USER_OWNER) != PackageManager.PERMISSION_GRANTED) {
+ continue;
+ }
+
+ // Select the IntentFilterVerifier with the highest priority
+ if (priority < info.priority) {
+ priority = info.priority;
+ verifierComponentName = new ComponentName(packageName, info.activityInfo.name);
+ Slog.d(TAG, "Selecting IntentFilterVerifier: " + verifierComponentName +
+ " with priority: " + info.priority);
+ }
+ }
+
+ return verifierComponentName;
+ }
+
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -3532,14 +3858,20 @@ public class PackageManagerService extends IPackageManager.Stub {
resolveInfo = queryCrossProfileIntents(
matchingFilters, intent, resolvedType, flags, userId);
- // Check for results in the current profile.
+ // Check for results in the current profile. Adding GET_RESOLVED_FILTER flags
+ // as we need it later
List<ResolveInfo> result = mActivities.queryIntent(
intent, resolvedType, flags, userId);
if (resolveInfo != null) {
result.add(resolveInfo);
Collections.sort(result, mResolvePrioritySorter);
}
- return filterIfNotPrimaryUser(result, userId);
+ result = filterIfNotPrimaryUser(result, userId);
+ if (result.size() > 1) {
+ return filterCandidatesWithDomainPreferedActivitiesLPw(result);
+ }
+
+ return result;
}
final PackageParser.Package pkg = mPackages.get(pkgName);
if (pkg != null) {
@@ -3570,6 +3902,49 @@ public class PackageManagerService extends IPackageManager.Stub {
return resolveInfos;
}
+ private List<ResolveInfo> filterCandidatesWithDomainPreferedActivitiesLPw(
+ List<ResolveInfo> candidates) {
+ if (DEBUG_PREFERRED) {
+ Slog.v("TAG", "Filtering results with prefered activities. Candidates count: " +
+ candidates.size());
+ }
+ final int userId = UserHandle.getCallingUserId();
+ ArrayList<ResolveInfo> result = new ArrayList<ResolveInfo>(candidates);
+ synchronized (mPackages) {
+ final int count = result.size();
+ for (int n = count-1; n >= 0; n--) {
+ ResolveInfo info = result.get(n);
+ if (!info.filterNeedsVerification) {
+ continue;
+ }
+ String packageName = info.activityInfo.packageName;
+ PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps != null) {
+ // Try to get the status from User settings first
+ int status = ps.getDomainVerificationStatusForUser(userId);
+ // if none available, get the master status
+ if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
+ if (ps.getIntentFilterVerificationInfo() != null) {
+ status = ps.getIntentFilterVerificationInfo().getStatus();
+ }
+ }
+ if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
+ result.clear();
+ result.add(info);
+ // We break the for loop as we are good to go
+ break;
+ } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
+ result.remove(n);
+ }
+ }
+ }
+ }
+ if (DEBUG_PREFERRED) {
+ Slog.v("TAG", "Filtered results with prefered activities. New candidates count: " +
+ result.size());
+ }
+ return result;
+ }
private ResolveInfo querySkipCurrentProfileIntents(
List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
@@ -7444,6 +7819,9 @@ public class PackageManagerService extends IPackageManager.Stub {
if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) {
res.filter = info;
}
+ if (info != null) {
+ res.filterNeedsVerification = info.needsVerification();
+ }
res.priority = info.getPriority();
res.preferredOrder = activity.owner.mPreferredOrder;
//System.out.println("Result: " + res.activityInfo.className +
@@ -7666,8 +8044,6 @@ public class PackageManagerService extends IPackageManager.Stub {
}
res.priority = info.getPriority();
res.preferredOrder = service.owner.mPreferredOrder;
- //System.out.println("Result: " + res.activityInfo.className +
- // " = " + res.priority);
res.match = match;
res.isDefault = info.hasDefault;
res.labelRes = info.labelRes;
@@ -8544,6 +8920,45 @@ public class PackageManagerService extends IPackageManager.Stub {
android.provider.Settings.Global.PACKAGE_VERIFIER_ENABLE, 1) == 1;
}
+ @Override
+ public void verifyIntentFilter(int id, int verificationCode, List<String> outFailedDomains)
+ throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT,
+ "Only intentfilter verification agents can verify applications");
+
+ final Message msg = mHandler.obtainMessage(INTENT_FILTER_VERIFIED);
+ final IntentFilterVerificationResponse response = new IntentFilterVerificationResponse(
+ Binder.getCallingUid(), verificationCode, outFailedDomains);
+ msg.arg1 = id;
+ msg.obj = response;
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public int getIntentVerificationStatus(String packageName, int userId) {
+ synchronized (mPackages) {
+ return mSettings.getIntentFilterVerificationStatusLPr(packageName, userId);
+ }
+ }
+
+ @Override
+ public boolean updateIntentVerificationStatus(String packageName, int status, int userId) {
+ boolean result = false;
+ synchronized (mPackages) {
+ result = mSettings.updateIntentFilterVerificationStatusLPw(packageName, status, userId);
+ }
+ scheduleWritePackageRestrictionsLocked(userId);
+ return result;
+ }
+
+ @Override
+ public List<IntentFilterVerificationInfo> getIntentFilterVerifications(String packageName) {
+ synchronized (mPackages) {
+ return mSettings.getIntentFilterVerificationsLPr(packageName);
+ }
+ }
+
/**
* Get the "allow unknown sources" setting.
*
@@ -10708,6 +11123,8 @@ public class PackageManagerService extends IPackageManager.Stub {
return;
}
+ startIntentFilterVerifications(args.user.getIdentifier(), pkg);
+
if (replace) {
replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
installerPackageName, res);
@@ -10723,6 +11140,86 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ private void startIntentFilterVerifications(int userId, PackageParser.Package pkg) {
+ if (mIntentFilterVerifierComponent == null) {
+ Slog.d(TAG, "No IntentFilter verification will not be done as "
+ + "there is no IntentFilterVerifier available!");
+ return;
+ }
+
+ final int verifierUid = getPackageUid(
+ mIntentFilterVerifierComponent.getPackageName(),
+ (userId == UserHandle.USER_ALL) ? UserHandle.USER_OWNER : userId);
+
+ mHandler.removeMessages(START_INTENT_FILTER_VERIFICATIONS);
+ final Message msg = mHandler.obtainMessage(START_INTENT_FILTER_VERIFICATIONS);
+ msg.obj = pkg;
+ msg.arg1 = userId;
+ msg.arg2 = verifierUid;
+
+ mHandler.sendMessage(msg);
+ }
+
+ private void verifyIntentFiltersIfNeeded(int userId, int verifierUid,
+ PackageParser.Package pkg) {
+ int size = pkg.activities.size();
+ if (size == 0) {
+ Slog.d(TAG, "No activity, so no need to verify any IntentFilter!");
+ return;
+ }
+
+ Slog.d(TAG, "Checking for userId:" + userId + " if any IntentFilter from the " + size
+ + " Activities needs verification ...");
+
+ final int verificationId = mIntentFilterVerificationToken++;
+ int count = 0;
+ synchronized (mPackages) {
+ for (PackageParser.Activity a : pkg.activities) {
+ for (ActivityIntentInfo filter : a.intents) {
+ boolean needFilterVerification = filter.needsVerification() &&
+ !filter.isVerified();
+ if (needFilterVerification && needNetworkVerificationLPr(filter)) {
+ Slog.d(TAG, "Verification needed for IntentFilter:" + filter.toString());
+ mIntentFilterVerifier.addOneIntentFilterVerification(
+ verifierUid, userId, verificationId, filter, pkg.packageName);
+ count++;
+ } else {
+ Slog.d(TAG, "No verification needed for IntentFilter:" + filter.toString());
+ }
+ }
+ }
+ }
+
+ if (count > 0) {
+ mIntentFilterVerifier.startVerifications(userId);
+ Slog.d(TAG, "Started " + count + " IntentFilter verification"
+ + (count > 1 ? "s" : "") + " for userId:" + userId + "!");
+ } else {
+ Slog.d(TAG, "No need to start any IntentFilter verification!");
+ }
+ }
+
+ private boolean needNetworkVerificationLPr(ActivityIntentInfo filter) {
+ final ComponentName cn = filter.activity.getComponentName();
+ final String packageName = cn.getPackageName();
+
+ IntentFilterVerificationInfo ivi = mSettings.getIntentFilterVerificationLPr(
+ packageName);
+ if (ivi == null) {
+ return true;
+ }
+ int status = ivi.getStatus();
+ switch (status) {
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
+ return true;
+
+ default:
+ // Nothing to do
+ return false;
+ }
+ }
+
private static boolean isMultiArch(PackageSetting ps) {
return (ps.pkgFlags & ApplicationInfo.FLAG_MULTIARCH) != 0;
}
@@ -11066,6 +11563,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
clearPackagePreferredActivitiesLPw(deletedPs.name, UserHandle.USER_ALL);
+ clearIntentFilterVerificationsLPw(deletedPs.name, UserHandle.USER_ALL);
}
// make sure to preserve per-user disabled state if this removal was just
// a downgrade of a system app to the factory package
@@ -11294,8 +11792,8 @@ public class PackageManagerService extends IPackageManager.Stub {
true, //notLaunched
false, //hidden
null, null, null,
- false // blockUninstall
- );
+ false, // blockUninstall
+ INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
if (!isSystemApp(ps)) {
if (ps.isAnyInstalled(sUserManager.getUserIds())) {
// Other user still have this package installed, so all
@@ -11918,6 +12416,19 @@ public class PackageManagerService extends IPackageManager.Stub {
return changed;
}
+ /** This method takes a specific user id as well as UserHandle.USER_ALL. */
+ void clearIntentFilterVerificationsLPw(String packageName, int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ mSettings.removeIntentFilterVerificationLPw(packageName, sUserManager.getUserIds());
+ for (int oneUserId : sUserManager.getUserIds()) {
+ scheduleWritePackageRestrictionsLocked(oneUserId);
+ }
+ } else {
+ mSettings.removeIntentFilterVerificationLPw(packageName, userId);
+ scheduleWritePackageRestrictionsLocked(userId);
+ }
+ }
+
@Override
public void resetPreferredActivities(int userId) {
/* TODO: Actually use userId. Why is it being passed in? */
@@ -12428,6 +12939,8 @@ public class PackageManagerService extends IPackageManager.Stub {
public static final int DUMP_KEYSETS = 1 << 11;
public static final int DUMP_VERSION = 1 << 12;
public static final int DUMP_INSTALLS = 1 << 13;
+ public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 14;
+ public static final int DUMP_DOMAIN_PREFERRED = 1 << 15;
public static final int OPTION_SHOW_FILTERS = 1 << 0;
@@ -12533,6 +13046,8 @@ public class PackageManagerService extends IPackageManager.Stub {
pw.println(" write: write current settings now");
pw.println(" <package.name>: info about given package");
pw.println(" installs: details about install sessions");
+ pw.println(" d[omain-preferred-apps]: print domains preferred apps");
+ pw.println(" i[ntent-filter-verifiers]|ifv: print intent filter verifier info");
return;
} else if ("--checkin".equals(opt)) {
checkin = true;
@@ -12569,6 +13084,8 @@ public class PackageManagerService extends IPackageManager.Stub {
fullPreferred = true;
opti++;
}
+ } else if ("d".equals(cmd) || "domain-preferred-apps".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_DOMAIN_PREFERRED);
} else if ("p".equals(cmd) || "packages".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_PACKAGES);
} else if ("s".equals(cmd) || "shared-users".equals(cmd)) {
@@ -12579,6 +13096,9 @@ public class PackageManagerService extends IPackageManager.Stub {
dumpState.setDump(DumpState.DUMP_MESSAGES);
} else if ("v".equals(cmd) || "verifiers".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_VERIFIERS);
+ } else if ("i".equals(cmd) || "ifv".equals(cmd)
+ || "intent-filter-verifiers".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_INTENT_FILTER_VERIFIERS);
} else if ("version".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_VERSION);
} else if ("k".equals(cmd) || "keysets".equals(cmd)) {
@@ -12634,6 +13154,29 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ if (dumpState.isDumping(DumpState.DUMP_INTENT_FILTER_VERIFIERS) &&
+ packageName == null) {
+ if (mIntentFilterVerifierComponent != null) {
+ String verifierPackageName = mIntentFilterVerifierComponent.getPackageName();
+ if (!checkin) {
+ if (dumpState.onTitlePrinted())
+ pw.println();
+ pw.println("Intent Filter Verifier:");
+ pw.print(" Using: ");
+ pw.print(verifierPackageName);
+ pw.print(" (uid=");
+ pw.print(getPackageUid(verifierPackageName, 0));
+ pw.println(")");
+ } else if (verifierPackageName != null) {
+ pw.print("ifv,"); pw.print(verifierPackageName);
+ pw.print(","); pw.println(getPackageUid(verifierPackageName, 0));
+ }
+ } else {
+ pw.println();
+ pw.println("No Intent Filter Verifier available!");
+ }
+ }
+
if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) {
boolean printedHeader = false;
final Iterator<String> it = mSharedLibraries.keySet().iterator();
@@ -12753,6 +13296,65 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)) {
+ pw.println();
+ int count = mSettings.mPackages.size();
+ if (count == 0) {
+ pw.println("No domain preferred apps!");
+ pw.println();
+ } else {
+ final String prefix = " ";
+ Collection<PackageSetting> allPackageSettings = mSettings.mPackages.values();
+ if (allPackageSettings.size() == 0) {
+ pw.println("No domain preferred apps!");
+ pw.println();
+ } else {
+ pw.println("Domain preferred apps status:");
+ pw.println();
+ count = 0;
+ for (PackageSetting ps : allPackageSettings) {
+ IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
+ if (ivi == null || ivi.getPackageName() == null) continue;
+ pw.println(prefix + "Package Name: " + ivi.getPackageName());
+ pw.println(prefix + "Domains: " + ivi.getDomainsString());
+ pw.println(prefix + "Status: " + ivi.getStatusString());
+ pw.println();
+ count++;
+ }
+ if (count == 0) {
+ pw.println(prefix + "No domain preferred app status!");
+ pw.println();
+ }
+ for (int userId : sUserManager.getUserIds()) {
+ pw.println("Domain preferred apps for User " + userId + ":");
+ pw.println();
+ count = 0;
+ for (PackageSetting ps : allPackageSettings) {
+ IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
+ if (ivi == null || ivi.getPackageName() == null) {
+ continue;
+ }
+ final int status = ps.getDomainVerificationStatusForUser(userId);
+ if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
+ continue;
+ }
+ pw.println(prefix + "Package Name: " + ivi.getPackageName());
+ pw.println(prefix + "Domains: " + ivi.getDomainsString());
+ String statusStr = IntentFilterVerificationInfo.
+ getStatusStringFromValue(status);
+ pw.println(prefix + "Status: " + statusStr);
+ pw.println();
+ count++;
+ }
+ if (count == 0) {
+ pw.println(prefix + "No domain preferred apps!");
+ pw.println();
+ }
+ }
+ }
+ }
+ }
+
if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
mSettings.dumpPermissionsLPr(pw, packageName, dumpState);
if (packageName == null) {
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 35df33b..20120de 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -20,6 +20,8 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.PackageUserState;
import android.util.ArraySet;
import android.util.SparseArray;
@@ -108,6 +110,9 @@ abstract class PackageSettingBase extends SettingBase {
/* package name of the app that installed this package */
String installerPackageName;
+
+ IntentFilterVerificationInfo verificationInfo;
+
PackageSettingBase(String name, String realName, File codePath, File resourcePath,
String legacyNativeLibraryPathString, String primaryCpuAbiString,
String secondaryCpuAbiString, String cpuAbiOverrideString,
@@ -214,6 +219,7 @@ abstract class PackageSettingBase extends SettingBase {
}
installStatus = base.installStatus;
keySetData = base.keySetData;
+ verificationInfo = base.verificationInfo;
}
private PackageUserState modifyUserState(int userId) {
@@ -317,7 +323,7 @@ abstract class PackageSettingBase extends SettingBase {
void setUserState(int userId, int enabled, boolean installed, boolean stopped,
boolean notLaunched, boolean hidden,
String lastDisableAppCaller, ArraySet<String> enabledComponents,
- ArraySet<String> disabledComponents, boolean blockUninstall) {
+ ArraySet<String> disabledComponents, boolean blockUninstall, int domainVerifState) {
PackageUserState state = modifyUserState(userId);
state.enabled = enabled;
state.installed = installed;
@@ -328,6 +334,7 @@ abstract class PackageSettingBase extends SettingBase {
state.enabledComponents = enabledComponents;
state.disabledComponents = disabledComponents;
state.blockUninstall = blockUninstall;
+ state.domainVerificationStatus = domainVerifState;
}
ArraySet<String> getEnabledComponents(int userId) {
@@ -415,4 +422,25 @@ abstract class PackageSettingBase extends SettingBase {
void removeUser(int userId) {
userState.delete(userId);
}
+
+ public IntentFilterVerificationInfo getIntentFilterVerificationInfo() {
+ return verificationInfo;
+ }
+
+ public void setIntentFilterVerificationInfo(IntentFilterVerificationInfo info) {
+ verificationInfo = info;
+ }
+
+ public int getDomainVerificationStatusForUser(int userId) {
+ return readUserState(userId).domainVerificationStatus;
+ }
+
+ public void setDomainVerificationStatusForUser(int status, int userId) {
+ modifyUserState(userId).domainVerificationStatus = status;
+ }
+
+ public void clearDomainVerificationStatusForUser(int userId) {
+ modifyUserState(userId).domainVerificationStatus =
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 8f185ec..dd58813 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -22,11 +22,13 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.PACKAGE_INFO_GID;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
+import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Binder;
@@ -41,6 +43,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.AtomicFile;
+import android.text.TextUtils;
import android.util.LogPrinter;
import android.util.SparseBooleanArray;
@@ -88,6 +91,7 @@ import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
@@ -160,6 +164,7 @@ final class Settings {
"persistent-preferred-activities";
static final String TAG_CROSS_PROFILE_INTENT_FILTERS =
"crossProfile-intent-filters";
+ public static final String TAG_DOMAIN_VERIFICATION = "domain-verification";
private static final String ATTR_NAME = "name";
private static final String ATTR_USER = "user";
@@ -174,6 +179,7 @@ final class Settings {
private static final String ATTR_HIDDEN = "hidden";
private static final String ATTR_INSTALLED = "inst";
private static final String ATTR_BLOCK_UNINSTALL = "blockUninstall";
+ private static final String ATTR_DOMAIN_VERIFICATON_STATE = "domainVerificationStatus";
private final Object mLock;
@@ -590,8 +596,8 @@ final class Settings {
true, // notLaunched
false, // hidden
null, null, null,
- false // blockUninstall
- );
+ false, // blockUninstall
+ INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
writePackageRestrictionsLPr(user.id);
}
}
@@ -865,7 +871,7 @@ final class Settings {
if (mOtherUserIds.get(uid) != null) {
PackageManagerService.reportSettingsProblem(Log.ERROR,
"Adding duplicate shared id: " + uid
- + " name=" + name);
+ + " name=" + name);
return false;
}
mOtherUserIds.put(uid, obj);
@@ -931,6 +937,96 @@ final class Settings {
return cpir;
}
+ /**
+ * The following functions suppose that you have a lock for managing access to the
+ * mIntentFiltersVerifications map.
+ */
+
+ /* package protected */
+ IntentFilterVerificationInfo getIntentFilterVerificationLPr(String packageName) {
+ PackageSetting ps = mPackages.get(packageName);
+ if (ps == null) {
+ Slog.w(PackageManagerService.TAG, "No package known for name: " + packageName);
+ return null;
+ }
+ return ps.getIntentFilterVerificationInfo();
+ }
+
+ /* package protected */
+ boolean createIntentFilterVerificationIfNeededLPw(String packageName, String[] domains) {
+ PackageSetting ps = mPackages.get(packageName);
+ if (ps == null) {
+ Slog.w(PackageManagerService.TAG, "No package known for name: " + packageName);
+ return false;
+ }
+ if (ps.getIntentFilterVerificationInfo() == null) {
+ IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(packageName, domains);
+ ps.setIntentFilterVerificationInfo(ivi);
+ return false;
+ }
+ return true;
+ }
+
+ int getIntentFilterVerificationStatusLPr(String packageName, int userId) {
+ PackageSetting ps = mPackages.get(packageName);
+ if (ps == null) {
+ Slog.w(PackageManagerService.TAG, "No package known for name: " + packageName);
+ return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
+ int status = ps.getDomainVerificationStatusForUser(userId);
+ if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
+ if (ps.getIntentFilterVerificationInfo() != null) {
+ status = ps.getIntentFilterVerificationInfo().getStatus();
+ }
+ }
+ return status;
+ }
+
+ boolean updateIntentFilterVerificationStatusLPw(String packageName, int status, int userId) {
+ PackageSetting ps = mPackages.get(packageName);
+ if (ps == null) {
+ Slog.w(PackageManagerService.TAG, "No package known for name: " + packageName);
+ return false;
+ }
+ ps.setDomainVerificationStatusForUser(status, userId);
+ return true;
+ }
+
+ /**
+ * Used for dump. Should be read only.
+ */
+ List<IntentFilterVerificationInfo> getIntentFilterVerificationsLPr(
+ String packageName) {
+ if (packageName == null) {
+ return Collections.<IntentFilterVerificationInfo>emptyList();
+ }
+ ArrayList<IntentFilterVerificationInfo> result = new ArrayList<>();
+ for (PackageSetting ps : mPackages.values()) {
+ IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
+ if (ivi == null || TextUtils.isEmpty(ivi.getPackageName()) ||
+ !ivi.getPackageName().equalsIgnoreCase(packageName)) {
+ continue;
+ }
+ result.add(ivi);
+ }
+ return result;
+ }
+
+ void removeIntentFilterVerificationLPw(String packageName, int userId) {
+ PackageSetting ps = mPackages.get(packageName);
+ if (ps == null) {
+ Slog.w(PackageManagerService.TAG, "No package known for name: " + packageName);
+ return;
+ }
+ ps.clearDomainVerificationStatusForUser(userId);
+ }
+
+ void removeIntentFilterVerificationLPw(String packageName, int[] userIds) {
+ for (int userId : userIds) {
+ removeIntentFilterVerificationLPw(packageName, userId);
+ }
+ }
+
private File getUserPackagesStateFile(int userId) {
// TODO: Implement a cleaner solution when adding tests.
// This instead of Environment.getUserSystemDirectory(userId) to support testing.
@@ -1083,6 +1179,25 @@ final class Settings {
}
}
+ private void readDomainVerificationLPw(XmlPullParser parser, PackageSettingBase packageSetting)
+ throws XmlPullParserException, IOException {
+ IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
+ packageSetting.setIntentFilterVerificationInfo(ivi);
+ Log.d(TAG, "Read domain verification for package:" + ivi.getPackageName());
+ }
+
+ void writeDomainVerificationsLPr(XmlSerializer serializer, String packageName,
+ IntentFilterVerificationInfo verificationInfo)
+ throws IllegalArgumentException, IllegalStateException, IOException {
+ if (verificationInfo != null && verificationInfo.getPackageName() != null) {
+ serializer.startTag(null, TAG_DOMAIN_VERIFICATION);
+ verificationInfo.writeToXml(serializer);
+ Log.d(TAG, "Wrote domain verification for package: "
+ + verificationInfo.getPackageName());
+ serializer.endTag(null, TAG_DOMAIN_VERIFICATION);
+ }
+ }
+
void readPackageRestrictionsLPr(int userId) {
if (DEBUG_MU) {
Log.i(TAG, "Reading package restrictions for user=" + userId);
@@ -1127,8 +1242,8 @@ final class Settings {
false, // notLaunched
false, // hidden
null, null, null,
- false // blockUninstall
- );
+ false, // blockUninstall
+ INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
}
return;
}
@@ -1197,6 +1312,12 @@ final class Settings {
final boolean blockUninstall = blockUninstallStr == null
? false : Boolean.parseBoolean(blockUninstallStr);
+ final String verifStateStr =
+ parser.getAttributeValue(null, ATTR_DOMAIN_VERIFICATON_STATE);
+ final int verifState = (verifStateStr == null) ?
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED :
+ Integer.parseInt(verifStateStr);
+
ArraySet<String> enabledComponents = null;
ArraySet<String> disabledComponents = null;
@@ -1217,7 +1338,8 @@ final class Settings {
}
ps.setUserState(userId, enabled, installed, stopped, notLaunched, hidden,
- enabledCaller, enabledComponents, disabledComponents, blockUninstall);
+ enabledCaller, enabledComponents, disabledComponents, blockUninstall,
+ verifState);
} else if (tagName.equals("preferred-activities")) {
readPreferredActivitiesLPw(parser, userId);
} else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
@@ -1364,7 +1486,9 @@ final class Settings {
&& ustate.enabledComponents.size() > 0)
|| (ustate.disabledComponents != null
&& ustate.disabledComponents.size() > 0)
- || ustate.blockUninstall) {
+ || ustate.blockUninstall
+ || (ustate.domainVerificationStatus !=
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED)) {
serializer.startTag(null, TAG_PACKAGE);
serializer.attribute(null, ATTR_NAME, pkg.name);
if (DEBUG_MU) Log.i(TAG, " pkg=" + pkg.name + ", state=" + ustate.enabled);
@@ -1392,6 +1516,11 @@ final class Settings {
ustate.lastDisableAppCaller);
}
}
+ if (ustate.domainVerificationStatus !=
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
+ serializer.attribute(null, ATTR_DOMAIN_VERIFICATON_STATE,
+ Integer.toString(ustate.domainVerificationStatus));
+ }
if (ustate.enabledComponents != null
&& ustate.enabledComponents.size() > 0) {
serializer.startTag(null, TAG_ENABLED_COMPONENTS);
@@ -1418,9 +1547,7 @@ final class Settings {
}
writePreferredActivitiesLPr(serializer, userId, true);
-
writePersistentPreferredActivitiesLPr(serializer, userId);
-
writeCrossProfileIntentFiltersLPr(serializer, userId);
serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS);
@@ -1933,6 +2060,7 @@ final class Settings {
writeSigningKeySetsLPr(serializer, pkg.keySetData);
writeUpgradeKeySetsLPr(serializer, pkg.keySetData);
writeKeySetAliasesLPr(serializer, pkg.keySetData);
+ writeDomainVerificationsLPr(serializer, pkg.name, pkg.verificationInfo);
serializer.endTag(null, "package");
}
@@ -2109,7 +2237,8 @@ final class Settings {
// TODO: check whether this is okay! as it is very
// similar to how preferred-activities are treated
readCrossProfileIntentFiltersLPw(parser, 0);
- } else if (tagName.equals("updated-package")) {
+ }
+ else if (tagName.equals("updated-package")) {
readDisabledSysPackageLPw(parser);
} else if (tagName.equals("cleaning-package")) {
String name = parser.getAttributeValue(null, ATTR_NAME);
@@ -3024,6 +3153,8 @@ final class Settings {
long id = Long.parseLong(parser.getAttributeValue(null, "identifier"));
String alias = parser.getAttributeValue(null, "alias");
packageSetting.keySetData.addDefinedKeySet(id, alias);
+ } else if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
+ readDomainVerificationLPw(parser, packageSetting);
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Unknown element under <package>: " + parser.getName());
@@ -3388,7 +3519,7 @@ final class Settings {
return false;
}
- private List<UserInfo> getAllUsers() {
+ List<UserInfo> getAllUsers() {
long id = Binder.clearCallingIdentity();
try {
return UserManagerService.getInstance().getUsers(false);
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 67a8c2b..a204376 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -31,6 +31,7 @@ import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
+import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
import android.content.pm.ManifestDigest;
import android.content.pm.PackageInfo;
@@ -725,6 +726,38 @@ public class MockPackageManager extends PackageManager {
* @hide
*/
@Override
+ public void verifyIntentFilter(int id, int verificationCode, List<String> outFailedDomains) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int getIntentVerificationStatus(String packageName, int userId) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean updateIntentVerificationStatus(String packageName, int status, int userId) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public List<IntentFilterVerificationInfo> getIntentFilterVerifications(String packageName) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
public VerifierDeviceIdentity getVerifierDeviceIdentity() {
throw new UnsupportedOperationException();
}