From f9e2ad0b3c2efb3bd93bb8b4ce2580512563005a Mon Sep 17 00:00:00 2001 From: Svetoslav Ganov Date: Wed, 29 Jun 2016 17:31:44 -0700 Subject: [PATCH] Add Bluetooth toggle prompts - framework If permission review is enabled toggling bluetoth on or off results in a user prompt to collect consent. This applies only to legacy apps, i.e. ones that don't support runtime permissions as they target SDK 22. Also added a configuration resource which controls whether permission review mode is enabled. By default it is not and an OEM can change this via an overlay. For now we also keep the old mechanism to toggle review mode via a build property which is still used and will be removed when clients have transitioned. bug:28715749 Change-Id: I94c5828ad6c8aa6b363622a26ff9da4fc2e2fac7 (cherry picked from commit ac69be543f89ea6e9a27204492e0a170d9b3450e) --- .../android/bluetooth/BluetoothAdapter.java | 32 ++++++- .../android/bluetooth/IBluetoothManager.aidl | 5 +- core/res/res/values/config.xml | 7 ++ core/res/res/values/symbols.xml | 3 + .../server/BluetoothManagerService.java | 83 +++++++++++++++---- .../com/android/server/am/ActiveServices.java | 4 +- .../server/am/ActivityManagerService.java | 7 +- .../android/server/am/ActivityStarter.java | 3 +- .../com/android/server/am/BroadcastQueue.java | 5 +- .../server/pm/PackageManagerService.java | 24 ++++-- .../android/server/power/ShutdownThread.java | 2 +- 11 files changed, 140 insertions(+), 35 deletions(-) diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index cd1a3dd02b614..073196cf7c0cc 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -23,6 +23,7 @@ import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; +import android.app.ActivityThread; import android.bluetooth.le.BluetoothLeAdvertiser; import android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.ScanCallback; @@ -251,6 +252,29 @@ public final class BluetoothAdapter { public static final String ACTION_REQUEST_ENABLE = "android.bluetooth.adapter.action.REQUEST_ENABLE"; + /** + * Activity Action: Show a system activity that allows the user to turn off + * Bluetooth. This is used only if permission review is enabled which is for + * apps targeting API less than 23 require a permission review before any of + * the app's components can run. + *

This system activity will return once Bluetooth has completed turning + * off, or the user has decided not to turn Bluetooth off. + *

Notification of the result of this activity is posted using the + * {@link android.app.Activity#onActivityResult} callback. The + * resultCode + * will be {@link android.app.Activity#RESULT_OK} if Bluetooth has been + * turned off or {@link android.app.Activity#RESULT_CANCELED} if the user + * has rejected the request or an error has occurred. + *

Applications can also listen for {@link #ACTION_STATE_CHANGED} + * for global notification whenever Bluetooth is turned on or off. + *

Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_REQUEST_DISABLE = + "android.bluetooth.adapter.action.REQUEST_DISABLE"; + /** * Activity Action: Show a system activity that allows user to enable BLE scans even when * Bluetooth is turned off.

@@ -775,7 +799,7 @@ public final class BluetoothAdapter { try { if (DBG) Log.d(TAG, "Calling enableBLE"); mManagerService.updateBleAppCount(mToken, true); - return mManagerService.enable(); + return mManagerService.enable(ActivityThread.currentPackageName()); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -902,7 +926,7 @@ public final class BluetoothAdapter { return true; } try { - return mManagerService.enable(); + return mManagerService.enable(ActivityThread.currentPackageName()); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -934,7 +958,7 @@ public final class BluetoothAdapter { @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) public boolean disable() { try { - return mManagerService.disable(true); + return mManagerService.disable(ActivityThread.currentPackageName(), true); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -952,7 +976,7 @@ public final class BluetoothAdapter { public boolean disable(boolean persist) { try { - return mManagerService.disable(persist); + return mManagerService.disable(ActivityThread.currentPackageName(), persist); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } diff --git a/core/java/android/bluetooth/IBluetoothManager.aidl b/core/java/android/bluetooth/IBluetoothManager.aidl index 0b81ee8c547e3..f39ca8e0e9c86 100644 --- a/core/java/android/bluetooth/IBluetoothManager.aidl +++ b/core/java/android/bluetooth/IBluetoothManager.aidl @@ -34,9 +34,9 @@ interface IBluetoothManager void registerStateChangeCallback(in IBluetoothStateChangeCallback callback); void unregisterStateChangeCallback(in IBluetoothStateChangeCallback callback); boolean isEnabled(); - boolean enable(); + boolean enable(String packageName); boolean enableNoAutoConnect(); - boolean disable(boolean persist); + boolean disable(String packageName, boolean persist); IBluetoothGatt getBluetoothGatt(); boolean bindBluetoothProfileService(int profile, IBluetoothProfileServiceConnection proxy); @@ -49,3 +49,4 @@ interface IBluetoothManager int updateBleAppCount(IBinder b, boolean enable); boolean isBleAppPresent(); } + diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 2b39f1876ee3a..0201111e4a22b 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2502,4 +2502,11 @@ 405 + + false + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 23cd7e6fb6166..23458851acc10 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2616,4 +2616,7 @@ + + + diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 6575a2a86cb96..7875cb86ee781 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -35,10 +35,12 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.os.Binder; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -151,6 +153,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private final Map mProfileServices = new HashMap (); + private final boolean mPermissionReviewRequired; + private void registerForAirplaneMode(IntentFilter filter) { final ContentResolver resolver = mContext.getContentResolver(); final String airplaneModeRadios = Settings.Global.getString(resolver, @@ -243,6 +247,11 @@ class BluetoothManagerService extends IBluetoothManager.Stub { mHandler = new BluetoothHandler(IoThread.get().getLooper()); mContext = context; + + mPermissionReviewRequired = Build.PERMISSIONS_REVIEW_REQUIRED + || context.getResources().getBoolean( + com.android.internal.R.bool.config_permissionReviewRequired); + mBluetooth = null; mBluetoothBinder = null; mBluetoothGatt = null; @@ -626,15 +635,26 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return true; } - public boolean enable() { - if ((Binder.getCallingUid() != Process.SYSTEM_UID) && - (!checkIfCallerIsForegroundUser())) { - Slog.w(TAG,"enable(): not allowed for non-active and non system user"); - return false; + public boolean enable(String packageName) throws RemoteException { + final int callingUid = Binder.getCallingUid(); + final boolean callerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID; + + if (!callerSystem) { + if (!checkIfCallerIsForegroundUser()) { + Slog.w(TAG, "enable(): not allowed for non-active and non system user"); + return false; + } + + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, + "Need BLUETOOTH ADMIN permission"); + + if (!isEnabled() && mPermissionReviewRequired + && startConsentUiIfNeeded(packageName, callingUid, + BluetoothAdapter.ACTION_REQUEST_ENABLE)) { + return false; + } } - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH ADMIN permission"); if (DBG) { Slog.d(TAG,"enable(): mBluetooth =" + mBluetooth + " mBinding = " + mBinding + " mState = " + mState); @@ -650,14 +670,24 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return true; } - public boolean disable(boolean persist) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH ADMIN permissicacheNameAndAddresson"); + public boolean disable(String packageName, boolean persist) throws RemoteException { + final int callingUid = Binder.getCallingUid(); + final boolean callerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID; - if ((Binder.getCallingUid() != Process.SYSTEM_UID) && - (!checkIfCallerIsForegroundUser())) { - Slog.w(TAG,"disable(): not allowed for non-active and non system user"); - return false; + if (!callerSystem) { + if (!checkIfCallerIsForegroundUser()) { + Slog.w(TAG, "disable(): not allowed for non-active and non system user"); + return false; + } + + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, + "Need BLUETOOTH ADMIN permission"); + + if (isEnabled() && mPermissionReviewRequired + && startConsentUiIfNeeded(packageName, callingUid, + BluetoothAdapter.ACTION_REQUEST_DISABLE)) { + return false; + } } if (DBG) { @@ -678,6 +708,31 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return true; } + private boolean startConsentUiIfNeeded(String packageName, + int callingUid, String intentAction) throws RemoteException { + try { + // Validate the package only if we are going to use it + ApplicationInfo applicationInfo = mContext.getPackageManager() + .getApplicationInfoAsUser(packageName, + PackageManager.MATCH_DEBUG_TRIAGED_MISSING, + UserHandle.getUserId(callingUid)); + if (applicationInfo.uid != callingUid) { + throw new SecurityException("Package " + callingUid + + " not in uid " + callingUid); + } + + // Legacy apps in permission review mode trigger a user prompt + if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) { + Intent intent = new Intent(intentAction); + mContext.startActivity(intent); + return true; + } + } catch (PackageManager.NameNotFoundException e) { + throw new RemoteException(e.getMessage()); + } + return false; + } + public void unbindAndFinish() { if (DBG) { Slog.d(TAG,"unbindAndFinish(): " + mBluetooth + diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index d9148817881b3..2d534171b94d3 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -368,7 +368,7 @@ public final class ActiveServices { // we do not start the service and launch a review activity if the calling app // is in the foreground passing it a pending intent to start the service when // review is completed. - if (Build.PERMISSIONS_REVIEW_REQUIRED) { + if (mAm.mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) { if (!requestStartTargetPermissionsReviewIfNeededLocked(r, callingPackage, callingUid, service, callerFg, userId)) { return null; @@ -891,7 +891,7 @@ public final class ActiveServices { // we schedule binding to the service but do not start its process, then // we launch a review activity to which is passed a callback to invoke // when done to start the bound service's process to completing the binding. - if (Build.PERMISSIONS_REVIEW_REQUIRED) { + if (mAm.mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) { if (mAm.getPackageManagerInternalLocked().isPermissionsReviewRequired( s.packageName, s.userId)) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index fdde150136633..b59f4686998ea 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1558,6 +1558,8 @@ public final class ActivityManagerService extends ActivityManagerNative // being called for multiwindow assist in a single session. private int mViSessionId = 1000; + final boolean mPermissionReviewRequired; + final class KillHandler extends Handler { static final int KILL_PROCESS_GROUP_MSG = 4000; @@ -2584,6 +2586,9 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.i(TAG, "Memory class: " + ActivityManager.staticGetMemoryClass()); + mPermissionReviewRequired = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_permissionReviewRequired); + mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/); mHandlerThread.start(); @@ -10741,7 +10746,7 @@ public final class ActivityManagerService extends ActivityManagerNative // If permissions need a review before any of the app components can run, // we return no provider and launch a review activity if the calling app // is in the foreground. - if (Build.PERMISSIONS_REVIEW_REQUIRED) { + if (mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) { if (!requestTargetProviderPermissionsReviewIfNeededLocked(cpi, r, userId)) { return null; } diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 32a798b33bcf7..5bd6bed752814 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -410,7 +410,8 @@ class ActivityStarter { // If permissions need a review before any of the app components can run, we // launch the review activity and pass a pending intent to start the activity // we are to launching now after the review is completed. - if (Build.PERMISSIONS_REVIEW_REQUIRED && aInfo != null) { + if ((mService.mPermissionReviewRequired + || Build.PERMISSIONS_REVIEW_REQUIRED) && aInfo != null) { if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired( aInfo.packageName, userId)) { IIntentSender target = mService.getIntentSenderLocked( diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 8e33c4f4bc358..2e1958cc7ba89 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -618,7 +618,7 @@ public final class BroadcastQueue { // the broadcast and if the calling app is in the foreground and the broadcast is // explicit we launch the review UI passing it a pending intent to send the skipped // broadcast. - if (Build.PERMISSIONS_REVIEW_REQUIRED) { + if (mService.mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) { if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName, filter.owningUserId)) { r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED; @@ -1124,7 +1124,8 @@ public final class BroadcastQueue { // the broadcast and if the calling app is in the foreground and the broadcast is // explicit we launch the review UI passing it a pending intent to send the skipped // broadcast. - if (Build.PERMISSIONS_REVIEW_REQUIRED && !skip) { + if ((mService.mPermissionReviewRequired + || Build.PERMISSIONS_REVIEW_REQUIRED) && !skip) { if (!requestStartTargetPermissionsReviewIfNeededLocked(r, info.activityInfo.packageName, UserHandle.getUserId( info.activityInfo.applicationInfo.uid))) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 85cc12427cef4..70560c72bc612 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1119,6 +1119,8 @@ public class PackageManagerService extends IPackageManager.Stub { final @NonNull String mServicesSystemSharedLibraryPackageName; final @NonNull String mSharedSystemSharedLibraryPackageName; + final boolean mPermissionReviewRequired; + private final PackageUsage mPackageUsage = new PackageUsage(); private final CompilerStats mCompilerStats = new CompilerStats(); @@ -2053,6 +2055,10 @@ public class PackageManagerService extends IPackageManager.Stub { } mContext = context; + + mPermissionReviewRequired = context.getResources().getBoolean( + R.bool.config_permissionReviewRequired); + mFactoryTest = factoryTest; mOnlyCore = onlyCore; mMetrics = new DisplayMetrics(); @@ -3965,7 +3971,7 @@ public class PackageManagerService extends IPackageManager.Stub { // their permissions as always granted runtime ones since we need // to keep the review required permission flag per user while an // install permission's state is shared across all users. - if (Build.PERMISSIONS_REVIEW_REQUIRED + if ((mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M && bp.isRuntime()) { return; @@ -4076,7 +4082,7 @@ public class PackageManagerService extends IPackageManager.Stub { // their permissions as always granted runtime ones since we need // to keep the review required permission flag per user while an // install permission's state is shared across all users. - if (Build.PERMISSIONS_REVIEW_REQUIRED + if ((mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M && bp.isRuntime()) { return; @@ -9866,7 +9872,8 @@ public class PackageManagerService extends IPackageManager.Stub { // their permissions as always granted runtime ones since we need // to keep the review required permission flag per user while an // install permission's state is shared across all users. - if (!appSupportsRuntimePermissions && !Build.PERMISSIONS_REVIEW_REQUIRED) { + if (!appSupportsRuntimePermissions && !mPermissionReviewRequired + && !Build.PERMISSIONS_REVIEW_REQUIRED) { // For legacy apps dangerous permissions are install time ones. grant = GRANT_INSTALL; } else if (origPermissions.hasInstallPermission(bp.name)) { @@ -9952,7 +9959,7 @@ public class PackageManagerService extends IPackageManager.Stub { changedRuntimePermissionUserIds, userId); } // If the app supports runtime permissions no need for a review. - if (Build.PERMISSIONS_REVIEW_REQUIRED + if ((mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) && appSupportsRuntimePermissions && (flags & PackageManager .FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { @@ -9961,7 +9968,8 @@ public class PackageManagerService extends IPackageManager.Stub { changedRuntimePermissionUserIds = ArrayUtils.appendInt( changedRuntimePermissionUserIds, userId); } - } else if (Build.PERMISSIONS_REVIEW_REQUIRED + } else if ((mPermissionReviewRequired + || Build.PERMISSIONS_REVIEW_REQUIRED) && !appSupportsRuntimePermissions) { // For legacy apps that need a permission review, every new // runtime permission is granted but it is pending a review. @@ -16423,7 +16431,7 @@ public class PackageManagerService extends IPackageManager.Stub { // If permission review is enabled and this is a legacy app, mark the // permission as requiring a review as this is the initial state. int flags = 0; - if (Build.PERMISSIONS_REVIEW_REQUIRED + if ((mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) && ps.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) { flags |= FLAG_PERMISSION_REVIEW_REQUIRED; } @@ -20272,7 +20280,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); // permissions to keep per user flag state whether review is needed. // Hence, if a new user is added we have to propagate dangerous // permission grants for these legacy apps. - if (Build.PERMISSIONS_REVIEW_REQUIRED) { + if (mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) { updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_ALL); } @@ -20726,7 +20734,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); public boolean isPermissionsReviewRequired(String packageName, int userId) { synchronized (mPackages) { // If we do not support permission review, done. - if (!Build.PERMISSIONS_REVIEW_REQUIRED) { + if (!mPermissionReviewRequired && !Build.PERMISSIONS_REVIEW_REQUIRED) { return false; } diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index f7f79f324ecca..02dd53dc52436 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -546,7 +546,7 @@ public final class ShutdownThread extends Thread { bluetoothOff = bluetooth == null || !bluetooth.isEnabled(); if (!bluetoothOff) { Log.w(TAG, "Disabling Bluetooth..."); - bluetooth.disable(false); // disable but don't persist new state + bluetooth.disable(mContext.getPackageName(), false); // disable but don't persist new state } } catch (RemoteException ex) { Log.e(TAG, "RemoteException during bluetooth shutdown", ex);