diff options
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(); } |