summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Svetoslav Ganov <svetoslavganov@google.com> 2016-06-29 17:31:44 -0700
committer Marie Janssen <jamuraa@google.com> 2016-12-12 09:50:24 -0800
commitc2ac5bde6f6e9cc95a476a2404f020246516ccfc (patch)
treed2114ecda26fb6bf453a0e681082a01098de382e
parent021e8ff2afeedf1e096a0709867c615a955a978c (diff)
DO NOT MERGE 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: I77bca2305f9d0f20034b2c8fc5b58e0565d5e617
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java32
-rw-r--r--core/java/android/bluetooth/IBluetoothManager.aidl4
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--services/core/java/com/android/server/BluetoothManagerService.java90
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java4
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java7
-rw-r--r--services/core/java/com/android/server/am/ActivityStarter.java3
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueue.java5
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java24
-rw-r--r--services/core/java/com/android/server/power/ShutdownThread.java2
10 files changed, 135 insertions, 38 deletions
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index d419d0384f5c..61544e4f927a 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;
@@ -255,6 +256,29 @@ public final class BluetoothAdapter {
"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.
+ * <p>This system activity will return once Bluetooth has completed turning
+ * off, or the user has decided not to turn Bluetooth off.
+ * <p>Notification of the result of this activity is posted using the
+ * {@link android.app.Activity#onActivityResult} callback. The
+ * <code>resultCode</code>
+ * 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.
+ * <p>Applications can also listen for {@link #ACTION_STATE_CHANGED}
+ * for global notification whenever Bluetooth is turned on or off.
+ * <p>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.<p>
*
@@ -768,7 +792,7 @@ public final class BluetoothAdapter {
return true;
}
if (DBG) Log.d(TAG, "enableBLE(): Calling enable");
- return mManagerService.enable();
+ return mManagerService.enable(ActivityThread.currentPackageName());
} catch (RemoteException e) {
Log.e(TAG, "", e);
}
@@ -895,7 +919,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;
}
@@ -927,7 +951,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;
}
@@ -945,7 +969,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 2b853a373b52..2ab9ae80b442 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);
int getState();
IBluetoothGatt getBluetoothGatt();
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c26fac1c3c74..ddf8f25248a6 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2716,7 +2716,6 @@
<java-symbol type="bool" name="config_permissionReviewRequired" />
-
<java-symbol type="drawable" name="ic_restart" />
<java-symbol type="drawable" name="emergency_icon" />
@@ -2731,5 +2730,4 @@
<!-- Network Recommendation -->
<java-symbol type="array" name="config_networkRecommendationPackageNames" />
-
</resources>
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 75b2dd494b68..5cf9eead5de7 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -35,11 +35,13 @@ 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.Bundle;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -154,6 +156,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
private final Map <Integer, ProfileServiceConnections> mProfileServices =
new HashMap <Integer, ProfileServiceConnections>();
+ private final boolean mPermissionReviewRequired;
+
private void registerForAirplaneMode(IntentFilter filter) {
final ContentResolver resolver = mContext.getContentResolver();
final String airplaneModeRadios = Settings.Global.getString(resolver,
@@ -183,7 +187,12 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
final boolean bluetoothDisallowed =
newRestrictions.getBoolean(UserManager.DISALLOW_BLUETOOTH);
if ((mEnable || mEnableExternal) && bluetoothDisallowed) {
- disable(true);
+ try {
+ disable("android.os.UserManagerInternal", true);
+ } catch (RemoteException e) {
+ // Shouldn't happen: startConsentUiIfNeeded not called
+ // when from system.
+ }
}
}
};
@@ -257,6 +266,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;
@@ -665,7 +679,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
return true;
}
- public boolean enable() {
+ public boolean enable(String packageName) throws RemoteException {
if (isBluetoothDisallowed()) {
if (DBG) {
Slog.d(TAG,"enable(): not enabling - bluetooth disallowed");
@@ -673,14 +687,25 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
return false;
}
- if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
- (!checkIfCallerIsForegroundUser())) {
- Slog.w(TAG,"enable(): not allowed for non-active and non system user");
- return false;
+ 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);
@@ -696,14 +721,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) {
@@ -724,6 +759,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 10a538863c25..ae1aef650ba4 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -369,7 +369,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;
@@ -913,7 +913,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 f6ce503fa586..fa54a6117ae3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1575,6 +1575,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;
@@ -2622,6 +2624,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();
@@ -10824,7 +10829,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 115971f6fffc..907394e39b14 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -416,7 +416,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 362a3479a6bf..ea901ce28953 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -626,7 +626,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;
@@ -1132,7 +1132,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 87eafd7e16eb..fb06f0178dc4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1140,6 +1140,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();
@@ -2070,6 +2072,10 @@ public class PackageManagerService extends IPackageManager.Stub {
}
mContext = context;
+
+ mPermissionReviewRequired = context.getResources().getBoolean(
+ R.bool.config_permissionReviewRequired);
+
mFactoryTest = factoryTest;
mOnlyCore = onlyCore;
mMetrics = new DisplayMetrics();
@@ -4027,7 +4033,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;
@@ -4138,7 +4144,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;
@@ -10019,7 +10025,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)) {
@@ -10105,7 +10112,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) {
@@ -10114,7 +10121,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.
@@ -16709,7 +16717,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;
}
@@ -20578,7 +20586,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);
}
@@ -21032,7 +21040,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 44894ed99bb2..badee82becbd 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -547,7 +547,7 @@ public final class ShutdownThread extends Thread {
bluetooth.getState() == BluetoothAdapter.STATE_OFF;
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);