diff options
author | Steve Kondik <shade@chemlab.org> | 2013-06-12 00:47:55 -0700 |
---|---|---|
committer | Steve Kondik <shade@chemlab.org> | 2013-06-24 11:36:22 -0700 |
commit | e48fce6da7bd839d70cbf69abb2780c6d30ea7f6 (patch) | |
tree | 49dd160f5a5d10847fd446e0db7f31f2d800792f /services/java | |
parent | b429a08331e8ebea15113fe287f4e5c9478d7001 (diff) | |
download | frameworks_base-e48fce6da7bd839d70cbf69abb2780c6d30ea7f6.zip frameworks_base-e48fce6da7bd839d70cbf69abb2780c6d30ea7f6.tar.gz frameworks_base-e48fce6da7bd839d70cbf69abb2780c6d30ea7f6.tar.bz2 |
framework: Privacy Guard
* Introduce a new privacy feature which allows the user to run an
application with reduced visibility into his or her personal data.
* Adds a per-application flag and simple API to determine if this flag
is enabled for the current or calling process.
* This flag can be used by content providers to decide if they should
return a limited/empty dataset.
Change-Id: Id7c54d728e63acb2b02a2a9322930b54949f6c5d
Diffstat (limited to 'services/java')
7 files changed, 288 insertions, 5 deletions
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 0f08c56..4658156 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -16,6 +16,7 @@ package com.android.server; +import android.app.ActivityManagerNative; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -664,8 +665,20 @@ public class LocationManagerService extends ILocationManager.Stub implements Run mProvidersByName.remove(provider.getName()); } + private boolean isPrivacyGuardEnabled(int pid) { + try { + if (ActivityManagerNative.getDefault().isPrivacyGuardEnabledForProcess(pid)) { + Slog.i(TAG, "Location services unavailable under privacy guard for process pid=" + pid); + return true; + } + } catch (RemoteException e) { + // nothing + } + return false; + } private boolean isAllowedBySettingsLocked(String provider, int userId) { + if (userId != mCurrentUserId) { return false; } @@ -826,6 +839,9 @@ public class LocationManagerService extends ILocationManager.Stub implements Run */ @Override public List<String> getProviders(Criteria criteria, boolean enabledOnly) { + if (isPrivacyGuardEnabled(Binder.getCallingPid())) { + return new ArrayList<String>(0); + } int allowedResolutionLevel = getCallerAllowedResolutionLevel(); ArrayList<String> out; int callingUserId = UserHandle.getCallingUserId(); @@ -1224,7 +1240,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } boolean isProviderEnabled = isAllowedBySettingsLocked(name, UserHandle.getUserId(uid)); - if (isProviderEnabled) { + if (isProviderEnabled && !isPrivacyGuardEnabled(pid)) { applyRequirementsLocked(name); } else { // Notify the listener that updates are currently disabled @@ -1238,6 +1254,9 @@ public class LocationManagerService extends ILocationManager.Stub implements Run checkPackageName(packageName); final int pid = Binder.getCallingPid(); + if (isPrivacyGuardEnabled(pid)) { + return; + } final int uid = Binder.getCallingUid(); Receiver receiver = checkListenerOrIntent(listener, intent, pid, uid, packageName); @@ -1297,6 +1316,9 @@ public class LocationManagerService extends ILocationManager.Stub implements Run checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel, request.getProvider()); // no need to sanitize this request, as only the provider name is used + if (isPrivacyGuardEnabled(Binder.getCallingPid())) { + return null; + } long identity = Binder.clearCallingIdentity(); try { @@ -1349,8 +1371,13 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (D) Log.d(TAG, "requestGeofence: " + sanitizedRequest + " " + geofence + " " + intent); + if (isPrivacyGuardEnabled(Binder.getCallingPid())) { + return; + } + // geo-fence manager uses the public location API, need to clear identity int uid = Binder.getCallingUid(); + if (UserHandle.getUserId(uid) != UserHandle.USER_OWNER) { // temporary measure until geofences work for secondary users Log.w(TAG, "proximity alerts are currently available only to the primary user"); @@ -1372,6 +1399,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (D) Log.d(TAG, "removeGeofence: " + geofence + " " + intent); + if (isPrivacyGuardEnabled(Binder.getCallingPid())) { + return; + } + // geo-fence manager uses the public location API, need to clear identity long identity = Binder.clearCallingIdentity(); try { @@ -1390,6 +1421,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(), LocationManager.GPS_PROVIDER); + if (isPrivacyGuardEnabled(Binder.getCallingPid())) { + return false; + } + try { mGpsStatusProvider.addGpsStatusListener(listener); } catch (RemoteException e) { @@ -1401,6 +1436,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run @Override public void removeGpsStatusListener(IGpsStatusListener listener) { + if (isPrivacyGuardEnabled(Binder.getCallingPid())) { + return; + } + synchronized (mLock) { try { mGpsStatusProvider.removeGpsStatusListener(listener); @@ -1419,6 +1458,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(), provider); + if (isPrivacyGuardEnabled(Binder.getCallingPid())) { + return false; + } + // and check for ACCESS_LOCATION_EXTRA_COMMANDS if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS) != PackageManager.PERMISSION_GRANTED)) { @@ -1439,6 +1482,11 @@ public class LocationManagerService extends ILocationManager.Stub implements Run throw new SecurityException( "calling sendNiResponse from outside of the system is not allowed"); } + + if (isPrivacyGuardEnabled(Binder.getCallingPid())) { + return false; + } + try { return mNetInitiatedListener.sendNiResponse(notifId, userResponse); } catch (RemoteException e) { @@ -1461,6 +1509,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(), provider); + if (isPrivacyGuardEnabled(Binder.getCallingPid())) { + return null; + } + LocationProviderInterface p; synchronized (mLock) { p = mProvidersByName.get(provider); @@ -1476,6 +1528,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run provider); if (LocationManager.FUSED_PROVIDER.equals(provider)) return false; + if (isPrivacyGuardEnabled(Binder.getCallingPid())) { + return false; + } + long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index c068982..618f02c 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -120,6 +120,7 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; +import android.provider.Telephony.Sms.Intents; import android.text.format.Time; import android.util.EventLog; import android.util.Log; @@ -901,6 +902,9 @@ public final class ActivityManagerService extends ActivityManagerNative static final int CONTINUE_USER_SWITCH_MSG = 35; static final int USER_SWITCH_TIMEOUT_MSG = 36; + static final int POST_PRIVACY_NOTIFICATION_MSG = 40; + static final int CANCEL_PRIVACY_NOTIFICATION_MSG = 41; + static final int FIRST_ACTIVITY_STACK_MSG = 100; static final int FIRST_BROADCAST_QUEUE_MSG = 200; static final int FIRST_COMPAT_MODE_MSG = 300; @@ -1361,6 +1365,69 @@ public final class ActivityManagerService extends ActivityManagerNative timeoutUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2); break; } + case POST_PRIVACY_NOTIFICATION_MSG: { + INotificationManager inm = NotificationManager.getService(); + if (inm == null) { + return; + } + + ActivityRecord root = (ActivityRecord)msg.obj; + ProcessRecord process = root.app; + if (process == null) { + return; + } + + try { + Context context = mContext.createPackageContext(process.info.packageName, 0); + String text = mContext.getString(R.string.privacy_guard_notification_detail, + context.getApplicationInfo().loadLabel(context.getPackageManager())); + String title = mContext.getString(R.string.privacy_guard_notification); + + Intent infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.fromParts("package", root.packageName, null)); + + Notification notification = new Notification(); + notification.icon = com.android.internal.R.drawable.stat_notify_privacy_guard; + notification.when = 0; + notification.flags = Notification.FLAG_ONGOING_EVENT; + notification.priority = Notification.PRIORITY_LOW; + notification.defaults = 0; + notification.sound = null; + notification.vibrate = null; + notification.setLatestEventInfo(getUiContext(), + title, text, + PendingIntent.getActivityAsUser(mContext, 0, infoIntent, + PendingIntent.FLAG_CANCEL_CURRENT, null, + new UserHandle(root.userId))); + + try { + int[] outId = new int[1]; + inm.enqueueNotificationWithTag("android", null, + R.string.privacy_guard_notification, + notification, outId, root.userId); + } catch (RuntimeException e) { + Slog.w(ActivityManagerService.TAG, + "Error showing notification for privacy guard", e); + } catch (RemoteException e) { + } + } catch (NameNotFoundException e) { + Slog.w(TAG, "Unable to create context for privacy guard notification", e); + } + } break; + case CANCEL_PRIVACY_NOTIFICATION_MSG: { + INotificationManager inm = NotificationManager.getService(); + if (inm == null) { + return; + } + try { + inm.cancelNotificationWithTag("android", null, + R.string.privacy_guard_notification, msg.arg1); + } catch (RuntimeException e) { + Slog.w(ActivityManagerService.TAG, + "Error canceling notification for service", e); + } catch (RemoteException e) { + } + } break; } } }; @@ -7426,6 +7493,30 @@ public final class ActivityManagerService extends ActivityManagerNative return KEY_DISPATCHING_TIMEOUT; } + public boolean isPrivacyGuardEnabledForProcess(int pid) { + ProcessRecord proc; + synchronized (mPidsSelfLocked) { + proc = mPidsSelfLocked.get(pid); + } + if (proc == null) { + return false; + } + try { + return AppGlobals.getPackageManager().getPrivacyGuardSetting( + proc.info.packageName, proc.userId); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage(), e); + } + return false; + } + + public boolean isFilteredByPrivacyGuard(String intent) { + return Intents.SMS_RECEIVED_ACTION.equals(intent) || + Intents.DATA_SMS_RECEIVED_ACTION.equals(intent) || + Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION.equals(intent) || + Intents.SMS_CB_RECEIVED_ACTION.equals(intent); + } + public void registerProcessObserver(IProcessObserver observer) { enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER, "registerProcessObserver()"); diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 885ec89..4e9973f 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -1803,9 +1803,43 @@ final class ActivityStack { startSpecificActivityLocked(next, true, true); } + handlePrivacyGuardNotification(prev, next); + return true; } + private final void handlePrivacyGuardNotification(ActivityRecord prev, ActivityRecord next) { + boolean curPrivacy = false; + boolean prevPrivacy = false; + + if (next != null) { + try { + curPrivacy = AppGlobals.getPackageManager().getPrivacyGuardSetting( + next.packageName, next.userId); + } catch (RemoteException e) { + // nothing + } + } + if (prev != null) { + try { + prevPrivacy = AppGlobals.getPackageManager().getPrivacyGuardSetting( + prev.packageName, prev.userId); + } catch (RemoteException e) { + // nothing + } + } + if (prevPrivacy && !curPrivacy) { + Message msg = mService.mHandler.obtainMessage( + ActivityManagerService.CANCEL_PRIVACY_NOTIFICATION_MSG, prev.userId); + msg.sendToTarget(); + } else if ((!prevPrivacy && curPrivacy) || + (prevPrivacy && curPrivacy && !next.packageName.equals(prev.packageName))) { + Message msg = mService.mHandler.obtainMessage( + ActivityManagerService.POST_PRIVACY_NOTIFICATION_MSG, next); + msg.sendToTarget(); + } + } + private final void startActivityLocked(ActivityRecord r, boolean newTask, boolean doResume, boolean keepCurTransition, Bundle options) { final int NH = mHistory.size(); diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java index bada7f0..fc4ecda 100644 --- a/services/java/com/android/server/am/BroadcastQueue.java +++ b/services/java/com/android/server/am/BroadcastQueue.java @@ -21,6 +21,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import android.app.ActivityManager; +import android.app.ActivityManagerNative; import android.app.AppGlobals; import android.content.ComponentName; import android.content.IIntentReceiver; @@ -409,7 +410,19 @@ public class BroadcastQueue { skip = true; } } - + try { + if (!skip && mService.isFilteredByPrivacyGuard(r.intent.getAction()) && + ActivityManagerNative.getDefault().isPrivacyGuardEnabledForProcess(filter.receiverList.pid)) { + Slog.w(TAG, "Skipping broadcast of " + + r.intent.toString() + + " to " + filter.receiverList.app + + " (pid=" + filter.receiverList.pid + + ") due to privacy guard"); + skip = true; + } + } catch (RemoteException e) { + // nothing + } if (!skip) { // If this is not being sent as an ordered broadcast, then we // don't want to touch the fields that keep track of the current diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index 461c060..7702deb 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -8485,11 +8485,16 @@ public class PackageManagerService extends IPackageManager.Stub { // The caller is asking that the package only be deleted for a single // user. To do this, we just mark its uninstalled state and delete // its data. + boolean privacyGuard = android.provider.Settings.Secure.getIntForUser( + mContext.getContentResolver(), + android.provider.Settings.Secure.PRIVACY_GUARD_DEFAULT, + 0, user.getIdentifier()) == 1; ps.setUserState(user.getIdentifier(), COMPONENT_ENABLED_STATE_DEFAULT, false, //installed true, //stopped true, //notLaunched + privacyGuard, null, null); if (ps.isAnyInstalled(sUserManager.getUserIds())) { // Other user still have this package installed, so all @@ -9033,6 +9038,60 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override + public void setPrivacyGuardSetting(String appPackageName, + boolean enabled, int userId) { + if (!sUserManager.exists(userId)) return; + setPrivacyGuard(appPackageName, enabled, userId); + } + + @Override + public boolean getPrivacyGuardSetting(String packageName, int userId) { + if (!sUserManager.exists(userId)) return false; + int uid = Binder.getCallingUid(); + enforceCrossUserPermission(uid, userId, false, "get privacy guard"); + // reader + synchronized (mPackages) { + return mSettings.getPrivacyGuardSettingLPr(packageName, userId); + } + } + + private void setPrivacyGuard(final String packageName, + final boolean enabled, final int userId) { + PackageSetting pkgSetting; + final int uid = Binder.getCallingUid(); + final int permission = mContext.checkCallingPermission( + android.Manifest.permission.CHANGE_PRIVACY_GUARD_STATE); + final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); + enforceCrossUserPermission(uid, userId, false, "set privacy guard"); + + synchronized (mPackages) { + pkgSetting = mSettings.mPackages.get(packageName); + if (pkgSetting == null) { + throw new IllegalArgumentException( + "Unknown package: " + packageName); + } + // Allow root and verify that userId is not being specified by a different user + if (!allowedByPermission && !UserHandle.isSameApp(uid, pkgSetting.appId)) { + throw new SecurityException( + "Permission Denial: attempt to change privacy guard state from pid=" + + Binder.getCallingPid() + + ", uid=" + uid + ", package uid=" + pkgSetting.appId); + } + if (pkgSetting.isPrivacyGuard(userId) == enabled) { + // Nothing to do + return; + } + pkgSetting.setPrivacyGuard(enabled, userId); + mSettings.writePackageRestrictionsLPr(userId); + try { + ActivityManagerNative.getDefault().forceStopPackage(packageName, userId); + } catch (RemoteException e) { + //nothing + } + } + } + + @Override public void setApplicationEnabledSetting(String appPackageName, int newState, int flags, int userId) { if (!sUserManager.exists(userId)) return; diff --git a/services/java/com/android/server/pm/PackageSettingBase.java b/services/java/com/android/server/pm/PackageSettingBase.java index ae1b213..af45219 100644 --- a/services/java/com/android/server/pm/PackageSettingBase.java +++ b/services/java/com/android/server/pm/PackageSettingBase.java @@ -197,6 +197,14 @@ class PackageSettingBase extends GrantedPermissions { return readUserState(userId).enabled; } + void setPrivacyGuard(boolean enabled, int userId) { + modifyUserState(userId).privacyGuard = enabled; + } + + boolean isPrivacyGuard(int userId) { + return readUserState(userId).privacyGuard; + } + void setInstalled(boolean inst, int userId) { modifyUserState(userId).installed = inst; } @@ -249,13 +257,14 @@ class PackageSettingBase extends GrantedPermissions { } void setUserState(int userId, int enabled, boolean installed, boolean stopped, - boolean notLaunched, HashSet<String> enabledComponents, + boolean notLaunched, boolean privacyGuard, HashSet<String> enabledComponents, HashSet<String> disabledComponents) { PackageUserState state = modifyUserState(userId); state.enabled = enabled; state.installed = installed; state.stopped = stopped; state.notLaunched = notLaunched; + state.privacyGuard = privacyGuard; state.enabledComponents = enabledComponents; state.disabledComponents = disabledComponents; } diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java index 1f568fb..f2074b0 100644 --- a/services/java/com/android/server/pm/Settings.java +++ b/services/java/com/android/server/pm/Settings.java @@ -100,6 +100,7 @@ final class Settings { private static final String ATTR_ENABLED = "enabled"; private static final String ATTR_STOPPED = "stopped"; private static final String ATTR_INSTALLED = "inst"; + private static final String ATTR_PRIVACY_GUARD = "privacy-guard"; private final File mSettingsFilename; private final File mBackupSettingsFilename; @@ -443,10 +444,15 @@ final class Settings { final boolean installed = installUser == null || installUser.getIdentifier() == UserHandle.USER_ALL || installUser.getIdentifier() == user.id; + final boolean privacyGuard = android.provider.Settings.Secure.getIntForUser( + mContext.getContentResolver(), + android.provider.Settings.Secure.PRIVACY_GUARD_DEFAULT, + 0, user.id) == 1; p.setUserState(user.id, COMPONENT_ENABLED_STATE_DEFAULT, installed, true, // stopped, true, // notLaunched + privacyGuard, null, null); writePackageRestrictionsLPr(user.id); } @@ -844,6 +850,7 @@ final class Settings { true, // installed false, // stopped false, // notLaunched + false, // privacy guard null, null); } return; @@ -898,6 +905,9 @@ final class Settings { final String notLaunchedStr = parser.getAttributeValue(null, ATTR_NOT_LAUNCHED); final boolean notLaunched = stoppedStr == null ? false : Boolean.parseBoolean(notLaunchedStr); + final String privacyGuardStr = parser.getAttributeValue(null, ATTR_PRIVACY_GUARD); + final boolean privacyGuard = privacyGuardStr == null + ? false : Boolean.parseBoolean(privacyGuardStr); HashSet<String> enabledComponents = null; HashSet<String> disabledComponents = null; @@ -918,7 +928,7 @@ final class Settings { } } - ps.setUserState(userId, enabled, installed, stopped, notLaunched, + ps.setUserState(userId, enabled, installed, stopped, notLaunched, privacyGuard, enabledComponents, disabledComponents); } else if (tagName.equals("preferred-activities")) { readPreferredActivitiesLPw(parser, userId); @@ -1024,7 +1034,7 @@ final class Settings { for (final PackageSetting pkg : mPackages.values()) { PackageUserState ustate = pkg.readUserState(userId); - if (ustate.stopped || ustate.notLaunched || !ustate.installed + if (ustate.stopped || ustate.notLaunched || !ustate.installed || ustate.privacyGuard || ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT || (ustate.enabledComponents != null && ustate.enabledComponents.size() > 0) @@ -1047,6 +1057,9 @@ final class Settings { serializer.attribute(null, ATTR_ENABLED, Integer.toString(ustate.enabled)); } + if (ustate.privacyGuard) { + serializer.attribute(null, ATTR_PRIVACY_GUARD, "true"); + } if (ustate.enabledComponents != null && ustate.enabledComponents.size() > 0) { serializer.startTag(null, TAG_ENABLED_COMPONENTS); @@ -2437,6 +2450,14 @@ final class Settings { return pkg.installerPackageName; } + boolean getPrivacyGuardSettingLPr(String packageName, int userId) { + final PackageSetting pkg = mPackages.get(packageName); + if (pkg == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + return pkg.isPrivacyGuard(userId); + } + int getApplicationEnabledSettingLPr(String packageName, int userId) { final PackageSetting pkg = mPackages.get(packageName); if (pkg == null) { |