diff options
9 files changed, 441 insertions, 0 deletions
diff --git a/api/current.txt b/api/current.txt index 46ff80c..22e5695 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4750,6 +4750,8 @@ package android.app.admin { } public class DevicePolicyManager { + method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName); + method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String); method public java.util.List<android.content.ComponentName> getActiveAdmins(); method public boolean getCameraDisabled(android.content.ComponentName); method public int getCurrentFailedPasswordAttempts(); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 0cc878e..0f7465e 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -22,6 +22,7 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.ComponentName; import android.content.Context; +import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -1766,4 +1767,53 @@ public class DevicePolicyManager { } return null; } + + /** + * Called by a profile owner or device owner to add a default intent handler activity for + * intents that match a certain intent filter. This activity will remain the default intent + * handler even if the set of potential event handlers for the intent filter changes and if + * the intent preferences are reset. + * + * <p>The default disambiguation mechanism takes over if the activity is not installed + * (anymore). When the activity is (re)installed, it is automatically reset as default + * intent handler for the filter. + * + * <p>The calling device admin must be a profile owner or device owner. If it is not, a + * security exception will be thrown. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param filter The IntentFilter for which a default handler is added. + * @param activity The Activity that is added as default intent handler. + */ + public void addPersistentPreferredActivity(ComponentName admin, IntentFilter filter, + ComponentName activity) { + if (mService != null) { + try { + mService.addPersistentPreferredActivity(admin, filter, activity); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * Called by a profile owner or device owner to remove all persistent intent handler preferences + * associated with the given package that were set by {@link addPersistentPreferredActivity}. + * + * <p>The calling device admin must be a profile owner. If it is not, a security + * exception will be thrown. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param packageName The name of the package for which preferences are removed. + */ + public void clearPackagePersistentPreferredActivities(ComponentName admin, + String packageName) { + if (mService != null) { + try { + mService.clearPackagePersistentPreferredActivities(admin, packageName); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 9d189db..8119585 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -18,6 +18,7 @@ package android.app.admin; import android.content.ComponentName; +import android.content.IntentFilter; import android.os.RemoteCallback; /** @@ -109,4 +110,7 @@ interface IDevicePolicyManager { boolean installCaCert(in byte[] certBuffer); void uninstallCaCert(in byte[] certBuffer); + + void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity); + void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName); } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 20002ad..c9fb530 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -237,6 +237,10 @@ interface IPackageManager { int getPreferredActivities(out List<IntentFilter> outFilters, out List<ComponentName> outActivities, String packageName); + void addPersistentPreferredActivity(in IntentFilter filter, in ComponentName activity, int userId); + + void clearPackagePersistentPreferredActivities(String packageName, int userId); + /** * Report the set of 'Home' activity candidates, plus (if any) which of them * is the current "always use this one" setting. diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 84f0f2e..07a74cd 100755 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2777,6 +2777,63 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } + private ResolveInfo findPersistentPreferredActivityLP(Intent intent, String resolvedType, + int flags, List<ResolveInfo> query, boolean debug, int userId) { + final int N = query.size(); + PersistentPreferredIntentResolver ppir = mSettings.mPersistentPreferredActivities + .get(userId); + // Get the list of persistent preferred activities that handle the intent + if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for presistent preferred activities..."); + List<PersistentPreferredActivity> pprefs = ppir != null + ? ppir.queryIntent(intent, resolvedType, + (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId) + : null; + if (pprefs != null && pprefs.size() > 0) { + final int M = pprefs.size(); + for (int i=0; i<M; i++) { + final PersistentPreferredActivity ppa = pprefs.get(i); + if (DEBUG_PREFERRED || debug) { + Slog.v(TAG, "Checking PersistentPreferredActivity ds=" + + (ppa.countDataSchemes() > 0 ? ppa.getDataScheme(0) : "<none>") + + "\n component=" + ppa.mComponent); + ppa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " "); + } + final ActivityInfo ai = getActivityInfo(ppa.mComponent, + flags | PackageManager.GET_DISABLED_COMPONENTS, userId); + if (DEBUG_PREFERRED || debug) { + Slog.v(TAG, "Found persistent preferred activity:"); + if (ai != null) { + ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " "); + } else { + Slog.v(TAG, " null"); + } + } + if (ai == null) { + // This previously registered persistent preferred activity + // component is no longer known. Ignore it and do NOT remove it. + continue; + } + for (int j=0; j<N; j++) { + final ResolveInfo ri = query.get(j); + if (!ri.activityInfo.applicationInfo.packageName + .equals(ai.applicationInfo.packageName)) { + continue; + } + if (!ri.activityInfo.name.equals(ai.name)) { + continue; + } + // Found a persistent preference that can handle the intent. + if (DEBUG_PREFERRED || debug) { + Slog.v(TAG, "Returning persistent preferred activity: " + + ri.activityInfo.packageName + "/" + ri.activityInfo.name); + } + return ri; + } + } + } + return null; + } + ResolveInfo findPreferredActivity(Intent intent, String resolvedType, int flags, List<ResolveInfo> query, int priority, boolean always, boolean removeMatches, boolean debug, int userId) { @@ -2787,6 +2844,16 @@ public class PackageManagerService extends IPackageManager.Stub { intent = intent.getSelector(); } if (DEBUG_PREFERRED) intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION); + + // Try to find a matching persistent preferred activity. + ResolveInfo pri = findPersistentPreferredActivityLP(intent, resolvedType, flags, query, + debug, userId); + + // If a persistent preferred activity matched, use it. + if (pri != null) { + return pri; + } + PreferredIntentResolver pir = mSettings.mPreferredActivities.get(userId); // Get the list of preferred activities that handle the intent if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for preferred activities..."); @@ -10341,6 +10408,71 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override + public void addPersistentPreferredActivity(IntentFilter filter, ComponentName activity, + int userId) { + int callingUid = Binder.getCallingUid(); + if (callingUid != Process.SYSTEM_UID) { + throw new SecurityException( + "addPersistentPreferredActivity can only be run by the system"); + } + if (filter.countActions() == 0) { + Slog.w(TAG, "Cannot set a preferred activity with no filter actions"); + return; + } + synchronized (mPackages) { + Slog.i(TAG, "Adding persistent preferred activity " + activity + " for user " + userId + + " :"); + filter.dump(new LogPrinter(Log.INFO, TAG), " "); + mSettings.editPersistentPreferredActivitiesLPw(userId).addFilter( + new PersistentPreferredActivity(filter, activity)); + mSettings.writePackageRestrictionsLPr(userId); + } + } + + @Override + public void clearPackagePersistentPreferredActivities(String packageName, int userId) { + int callingUid = Binder.getCallingUid(); + if (callingUid != Process.SYSTEM_UID) { + throw new SecurityException( + "clearPackagePersistentPreferredActivities can only be run by the system"); + } + ArrayList<PersistentPreferredActivity> removed = null; + boolean changed = false; + synchronized (mPackages) { + for (int i=0; i<mSettings.mPersistentPreferredActivities.size(); i++) { + final int thisUserId = mSettings.mPersistentPreferredActivities.keyAt(i); + PersistentPreferredIntentResolver ppir = mSettings.mPersistentPreferredActivities + .valueAt(i); + if (userId != thisUserId) { + continue; + } + Iterator<PersistentPreferredActivity> it = ppir.filterIterator(); + while (it.hasNext()) { + PersistentPreferredActivity ppa = it.next(); + // Mark entry for removal only if it matches the package name. + if (ppa.mComponent.getPackageName().equals(packageName)) { + if (removed == null) { + removed = new ArrayList<PersistentPreferredActivity>(); + } + removed.add(ppa); + } + } + if (removed != null) { + for (int j=0; j<removed.size(); j++) { + PersistentPreferredActivity ppa = removed.get(j); + ppir.removeFilter(ppa); + } + changed = true; + } + } + + if (changed) { + mSettings.writePackageRestrictionsLPr(userId); + } + } + } + + @Override public ComponentName getHomeActivities(List<ResolveInfo> allHomeCandidates) { Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME); diff --git a/services/core/java/com/android/server/pm/PersistentPreferredActivity.java b/services/core/java/com/android/server/pm/PersistentPreferredActivity.java new file mode 100644 index 0000000..4284a6d --- /dev/null +++ b/services/core/java/com/android/server/pm/PersistentPreferredActivity.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2014 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 com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import android.content.ComponentName; +import android.content.IntentFilter; +import android.util.Log; + +import java.io.IOException; + +class PersistentPreferredActivity extends IntentFilter { + private static final String ATTR_NAME = "name"; // component name + private static final String ATTR_FILTER = "filter"; // filter + + private static final String TAG = "PersistentPreferredActivity"; + + private static final boolean DEBUG_FILTERS = false; + + final ComponentName mComponent; + + PersistentPreferredActivity(IntentFilter filter, ComponentName activity) { + super(filter); + mComponent = activity; + } + + PersistentPreferredActivity(XmlPullParser parser) throws XmlPullParserException, IOException { + String shortComponent = parser.getAttributeValue(null, ATTR_NAME); + mComponent = ComponentName.unflattenFromString(shortComponent); + if (mComponent == null) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings <hard-preferred-activity>: " + + "Bad activity name " + shortComponent + + " at " + parser.getPositionDescription()); + } + int outerDepth = parser.getDepth(); + String tagName = parser.getName(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + tagName = parser.getName(); + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } else if (type == XmlPullParser.START_TAG) { + if (tagName.equals(ATTR_FILTER)) { + break; + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <hard-preferred-activity>: " + tagName + + " at " + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + } + } + } + if (tagName.equals(ATTR_FILTER)) { + readFromXml(parser); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Missing element under <hard-preferred-activity>: filter at " + + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + } + } + + public void writeToXml(XmlSerializer serializer) throws IOException { + serializer.attribute(null, ATTR_NAME, mComponent.flattenToShortString()); + serializer.startTag(null, ATTR_FILTER); + super.writeToXml(serializer); + serializer.endTag(null, ATTR_FILTER); + } + + @Override + public String toString() { + return "PersistentPreferredActivity{0x" + Integer.toHexString(System.identityHashCode(this)) + + " " + mComponent.flattenToShortString() + "}"; + } +} diff --git a/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java b/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java new file mode 100644 index 0000000..9c8a9bd --- /dev/null +++ b/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2014 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.io.PrintWriter; + +import com.android.server.IntentResolver; + +public class PersistentPreferredIntentResolver + extends IntentResolver<PersistentPreferredActivity, PersistentPreferredActivity> { + @Override + protected PersistentPreferredActivity[] newArray(int size) { + return new PersistentPreferredActivity[size]; + } + + @Override + protected boolean isPackageForFilter(String packageName, PersistentPreferredActivity filter) { + return packageName.equals(filter.mComponent.getPackageName()); + } +} diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index e7c6446..9236bde 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -141,6 +141,11 @@ final class Settings { final SparseArray<PreferredIntentResolver> mPreferredActivities = new SparseArray<PreferredIntentResolver>(); + // The persistent preferred activities of the user's profile/device owner + // associated with particular intent filters. + final SparseArray<PersistentPreferredIntentResolver> mPersistentPreferredActivities = + new SparseArray<PersistentPreferredIntentResolver>(); + final HashMap<String, SharedUserSetting> mSharedUsers = new HashMap<String, SharedUserSetting>(); private final ArrayList<Object> mUserIds = new ArrayList<Object>(); @@ -776,6 +781,15 @@ final class Settings { return pir; } + PersistentPreferredIntentResolver editPersistentPreferredActivitiesLPw(int userId) { + PersistentPreferredIntentResolver ppir = mPersistentPreferredActivities.get(userId); + if (ppir == null) { + ppir = new PersistentPreferredIntentResolver(); + mPersistentPreferredActivities.put(userId, ppir); + } + return ppir; + } + private File getUserPackagesStateFile(int userId) { return new File(Environment.getUserSystemDirectory(userId), "package-restrictions.xml"); } @@ -835,6 +849,27 @@ final class Settings { } } + private void readPersistentPreferredActivitiesLPw(XmlPullParser parser, int userId) + throws XmlPullParserException, IOException { + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + String tagName = parser.getName(); + if (tagName.equals(TAG_ITEM)) { + PersistentPreferredActivity ppa = new PersistentPreferredActivity(parser); + editPersistentPreferredActivitiesLPw(userId).addFilter(ppa); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <hard-preferred-activities>: " + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + } + void readPackageRestrictionsLPr(int userId) { if (DEBUG_MU) { Log.i(TAG, "Reading package restrictions for user=" + userId); @@ -961,6 +996,8 @@ final class Settings { enabledCaller, enabledComponents, disabledComponents); } else if (tagName.equals("preferred-activities")) { readPreferredActivitiesLPw(parser, userId); + } else if (tagName.equals("hard-preferred-activities")) { + readPersistentPreferredActivitiesLPw(parser, userId); } else { Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: " + parser.getName()); @@ -1024,6 +1061,20 @@ final class Settings { serializer.endTag(null, "preferred-activities"); } + void writePersistentPreferredActivitiesLPr(XmlSerializer serializer, int userId) + throws IllegalArgumentException, IllegalStateException, IOException { + serializer.startTag(null, "hard-preferred-activities"); + PersistentPreferredIntentResolver ppir = mPersistentPreferredActivities.get(userId); + if (ppir != null) { + for (final PersistentPreferredActivity ppa : ppir.filterSet()) { + serializer.startTag(null, TAG_ITEM); + ppa.writeToXml(serializer); + serializer.endTag(null, TAG_ITEM); + } + } + serializer.endTag(null, "hard-preferred-activities"); + } + void writePackageRestrictionsLPr(int userId) { if (DEBUG_MU) { Log.i(TAG, "Writing package restrictions for user=" + userId); @@ -1120,6 +1171,8 @@ final class Settings { writePreferredActivitiesLPr(serializer, userId, true); + writePersistentPreferredActivitiesLPr(serializer, userId); + serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS); serializer.endDocument(); @@ -1721,6 +1774,10 @@ final class Settings { // Upgrading from old single-user implementation; // these are the preferred activities for user 0. readPreferredActivitiesLPw(parser, 0); + } else if (tagName.equals("hard-preferred-activities")) { + // TODO: check whether this is okay! as it is very + // similar to how preferred-activities are treated + readPersistentPreferredActivitiesLPw(parser, 0); } else if (tagName.equals("updated-package")) { readDisabledSysPackageLPw(parser); } else if (tagName.equals("cleaning-package")) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 12f0114..92ed75b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -2966,4 +2966,66 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } + + private boolean isProfileOwner(String packageName, int userId) { + String profileOwnerPackage = getProfileOwner(userId); + // TODO: make public and connect with isProfileOwnerApp in DPM + return profileOwnerPackage != null && profileOwnerPackage.equals(packageName); + } + + public void addPersistentPreferredActivity(ComponentName admin, IntentFilter filter, + ComponentName activity) { + int callingUserId = UserHandle.getCallingUserId(); + Slog.d(LOG_TAG,"called by user " + callingUserId); + synchronized (this) { + ActiveAdmin aa = getActiveAdminUncheckedLocked(admin, callingUserId); + if (aa == null) { + throw new SecurityException("No active admin " + admin); + } else { + if (isProfileOwner(admin.getPackageName(), callingUserId) + || isDeviceOwner(admin.getPackageName())) { + IPackageManager pm = AppGlobals.getPackageManager(); + long id = Binder.clearCallingIdentity(); + try { + pm.addPersistentPreferredActivity(filter, activity, callingUserId); + } catch (RemoteException re) { + // Shouldn't happen + } finally { + restoreCallingIdentity(id); + } + } else { + throw new SecurityException("Admin " + admin + + "is not device owner or profile owner" ); + } + } + } + } + + public void clearPackagePersistentPreferredActivities(ComponentName admin, + String packageName) { + int callingUserId = UserHandle.getCallingUserId(); + Slog.d(LOG_TAG,"called by user " + callingUserId); + synchronized (this) { + ActiveAdmin aa = getActiveAdminUncheckedLocked(admin, callingUserId); + if (aa == null) { + throw new SecurityException("No active admin " + admin); + } else { + if (isProfileOwner(admin.getPackageName(), callingUserId) + || isDeviceOwner(admin.getPackageName())) { + IPackageManager pm = AppGlobals.getPackageManager(); + long id = Binder.clearCallingIdentity(); + try{ + pm.clearPackagePersistentPreferredActivities(packageName, callingUserId); + } catch (RemoteException re) { + // Shouldn't happen + } finally { + restoreCallingIdentity(id); + } + } else { + throw new SecurityException("Admin " + admin + + "is not device owner or profile owner" ); + } + } + } + } } |