diff options
author | Sander Alewijnse <salewijnse@google.com> | 2014-02-17 15:13:58 +0000 |
---|---|---|
committer | Sander Alewijnse <salewijnse@google.com> | 2014-02-21 15:24:20 +0000 |
commit | f475ca33d9232785710aaa438f17915029dfa83b (patch) | |
tree | ca3ac28738e66281dc97546a9132a45cca69bc8f | |
parent | c4f6c351e18380e712d5d365d2a13cfa5674daf7 (diff) | |
download | frameworks_base-f475ca33d9232785710aaa438f17915029dfa83b.zip frameworks_base-f475ca33d9232785710aaa438f17915029dfa83b.tar.gz frameworks_base-f475ca33d9232785710aaa438f17915029dfa83b.tar.bz2 |
Enables a profile owner or device owner to set and clear default intent handler activities.
Those intent handlers are persistent preferences. They 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.
Change-Id: Id0cfae46f93c10d89e441f272096a205ec518dd0
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" ); + } + } + } + } } |