Handle Bluetooth Enable/Disable Intents
Add an activity to handle intents to enable/disable bluetooth.
Bug: 133189770
Test: Manual
Change-Id: I6106e09bdfdae3bcb806d63068b7f129d5789f2c
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7479f10..47c8256 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -262,6 +262,15 @@
</intent-filter>
</activity>
+ <activity android:name=".bluetooth.BluetoothRequestPermissionActivity"
+ android:excludeFromRecents="true">
+ <intent-filter>
+ <action android:name="android.bluetooth.adapter.action.REQUEST_ENABLE" />
+ <action android:name="android.bluetooth.adapter.action.REQUEST_DISABLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<activity android:name=".accounts.AddAccountActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:configChanges="orientation|keyboardHidden|screenSize"/>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6e5d4a1..e1d6842 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -293,6 +293,18 @@
<string name="bluetooth_profiles">Profiles</string>
<!-- Title for BT error dialogs. -->
<string name="bluetooth_error_title"></string>
+ <!-- Strings for msg to display to user while bluetooth is turning on [CHAR LIMIT=60] -->
+ <string name="bluetooth_turning_on">"Turning Bluetooth on\u2026"</string>
+ <!-- Strings for msg to display to user while bluetooth is turning off [CHAR LIMIT=60] -->
+ <string name="bluetooth_turning_off">"Turning Bluetooth off\u2026"</string>
+ <!-- This string asks the user whether or not to allow an app to enable bluetooth. [CHAR LIMIT=100] -->
+ <string name="bluetooth_ask_enablement"><xliff:g id="app_name" example="FancyApp">%1$s</xliff:g> wants to turn on Bluetooth</string>
+ <!-- This string asks the user whether or not to allow an app to disable bluetooth. [CHAR LIMIT=100] -->
+ <string name="bluetooth_ask_disablement"><xliff:g id="app_name" example="FancyApp">%1$s</xliff:g> wants to turn off Bluetooth</string>
+ <!-- This string asks the user whether or not to allow an app to enable bluetooth. [CHAR LIMIT=100] -->
+ <string name="bluetooth_ask_enablement_no_name">An app wants to turn on Bluetooth</string>
+ <!-- This string asks the user whether or not to allow an app to disable bluetooth. [CHAR LIMIT=100] -->
+ <string name="bluetooth_ask_disablement_no_name">An app wants to turn off Bluetooth</string>
<!-- Bluetooth pairing --><skip/>
<!-- Notification ticker text (shown in the status bar) when a Bluetooth device wants to pair with us -->
diff --git a/src/com/android/car/settings/bluetooth/BluetoothRequestPermissionActivity.java b/src/com/android/car/settings/bluetooth/BluetoothRequestPermissionActivity.java
new file mode 100644
index 0000000..c285c3d
--- /dev/null
+++ b/src/com/android/car/settings/bluetooth/BluetoothRequestPermissionActivity.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2019 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.car.settings.bluetooth;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.text.TextUtils;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.Logger;
+import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+/**
+ * Code drop from {@link com.android.settings.bluetooth.RequestPermissionActivity}.
+ *
+ * This {@link Activity} handles requests to toggle Bluetooth by collecting user
+ * consent and waiting until the state change is completed.
+ */
+public class BluetoothRequestPermissionActivity extends Activity implements
+ DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+ private static final Logger LOG = new Logger(BluetoothRequestPermissionActivity.class);
+ private static final int REQUEST_UNKNOWN = 0;
+ private static final int REQUEST_ENABLE = 1;
+ private static final int REQUEST_DISABLE = 2;
+ private int mRequest;
+ @NonNull
+ private CharSequence mAppLabel;
+ private LocalBluetoothAdapter mLocalBluetoothAdapter;
+ private LocalBluetoothManager mLocalBluetoothManager;
+ private AlertDialog mDialog;
+ private StateChangeReceiver mReceiver;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setResult(Activity.RESULT_CANCELED);
+
+ mRequest = parseIntent();
+ if (mRequest == REQUEST_UNKNOWN) {
+ finish();
+ return;
+ }
+
+ mReceiver = new StateChangeReceiver();
+
+ int btState = mLocalBluetoothAdapter.getState();
+ switch (mRequest) {
+ case REQUEST_DISABLE:
+ switch (btState) {
+ case BluetoothAdapter.STATE_OFF:
+ case BluetoothAdapter.STATE_TURNING_OFF:
+ proceedAndFinish();
+ break;
+
+ case BluetoothAdapter.STATE_ON:
+ case BluetoothAdapter.STATE_TURNING_ON:
+ createDecisionDialog();
+ break;
+
+ default:
+ LOG.e("Unknown adapter state: " + btState);
+ finish();
+ break;
+ }
+ break;
+ case REQUEST_ENABLE:
+ switch (btState) {
+ case BluetoothAdapter.STATE_OFF:
+ case BluetoothAdapter.STATE_TURNING_OFF:
+ createDecisionDialog();
+ break;
+
+ case BluetoothAdapter.STATE_ON:
+ proceedAndFinish();
+ break;
+ default:
+ LOG.e("Unknown adapter state: " + btState);
+ finish();
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mReceiver.register();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mReceiver.unregister();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case DialogInterface.BUTTON_POSITIVE:
+ proceedAndFinish();
+ break;
+
+ case DialogInterface.BUTTON_NEGATIVE:
+ onCancel(/* dialog = */ null);
+ break;
+ }
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ }
+
+ private void createInterimDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(/* context = */ this);
+
+ switch (mRequest) {
+ case REQUEST_ENABLE:
+ builder.setMessage(getString(R.string.bluetooth_turning_on));
+ break;
+ default:
+ builder.setMessage(getString(R.string.bluetooth_turning_off));
+ break;
+ }
+ builder.setCancelable(false).setOnCancelListener(/* listener = */ this);
+
+ mDialog = builder.create();
+ mDialog.show();
+ }
+
+ private void createDecisionDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(/* context= */ this);
+ switch (mRequest) {
+ case REQUEST_ENABLE:
+ builder.setMessage(
+ mAppLabel != null ? getString(R.string.bluetooth_ask_enablement,
+ mAppLabel)
+ : getString(R.string.bluetooth_ask_enablement_no_name));
+ break;
+
+ case REQUEST_DISABLE: {
+ builder.setMessage(
+ mAppLabel != null ? getString(R.string.bluetooth_ask_disablement, mAppLabel)
+ : getString(R.string.bluetooth_ask_disablement_no_name));
+ break;
+ }
+ }
+
+ builder.setPositiveButton(R.string.allow, this::decisionDialogPositiveButtonListener)
+ .setNegativeButton(R.string.deny, (dialog, which) -> onCancel(/* dialog = */ null))
+ .setOnCancelListener(/* listener = */ this);
+
+ mDialog = builder.create();
+ mDialog.show();
+ }
+
+ private void decisionDialogPositiveButtonListener(DialogInterface dialog, int which) {
+ dialog.dismiss();
+
+ if (!hasUserRestriction()) {
+ switch (mRequest) {
+ case REQUEST_ENABLE:
+ if (mLocalBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
+ proceedAndFinish();
+ } else {
+ // If BT is not up yet, show "Turning on Bluetooth..."
+ createInterimDialog();
+ }
+ break;
+
+ case REQUEST_DISABLE:
+ if (mLocalBluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF) {
+ proceedAndFinish();
+ } else {
+ // If BT is not up yet, show "Turning off Bluetooth..."
+ createInterimDialog();
+ }
+ break;
+
+ default:
+ finish();
+ break;
+ }
+ }
+ }
+
+ private boolean hasUserRestriction() {
+ switch (mRequest) {
+ case REQUEST_ENABLE:
+ UserManager userManager = getSystemService(UserManager.class);
+ if (userManager.hasUserRestriction(UserManager.DISALLOW_BLUETOOTH)) {
+ // If Bluetooth is disallowed, don't try to enable it, show policy
+ // transparency
+ // message instead.
+ DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
+ Intent intent = dpm.createAdminSupportIntent(
+ UserManager.DISALLOW_BLUETOOTH);
+ if (intent != null) {
+ startActivity(intent);
+ }
+
+ return true;
+ } else {
+ mLocalBluetoothAdapter.enable();
+ }
+ break;
+
+ case REQUEST_DISABLE: {
+ mLocalBluetoothAdapter.disable();
+ }
+ break;
+ }
+
+ return false;
+ }
+
+ private void proceedAndFinish() {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
+ finish();
+ }
+
+ private int parseIntent() {
+ int request = REQUEST_UNKNOWN;
+ Intent intent = getIntent();
+ if (intent == null) {
+ return REQUEST_UNKNOWN;
+ }
+
+ switch (intent.getAction()) {
+ case BluetoothAdapter.ACTION_REQUEST_ENABLE:
+ request = REQUEST_ENABLE;
+ break;
+ case BluetoothAdapter.ACTION_REQUEST_DISABLE:
+ request = REQUEST_DISABLE;
+ break;
+ default:
+ LOG.e("Error: this activity may be started only with intent "
+ + BluetoothAdapter.ACTION_REQUEST_ENABLE);
+ return REQUEST_UNKNOWN;
+ }
+
+ String packageName = getCallingPackage();
+ if (TextUtils.isEmpty(packageName)) {
+ packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ }
+ if (!TextUtils.isEmpty(packageName)) {
+ try {
+ ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
+ packageName, 0);
+ mAppLabel = applicationInfo.loadLabel(getPackageManager());
+ } catch (PackageManager.NameNotFoundException e) {
+ LOG.e("Couldn't find app with package name " + packageName);
+ return REQUEST_UNKNOWN;
+ }
+ }
+
+ mLocalBluetoothManager = LocalBluetoothManager.getInstance(
+ getApplicationContext(), /* onInitCallback= */ null);
+ if (mLocalBluetoothManager == null) {
+ LOG.e("Bluetooth is not supported on this device");
+ return REQUEST_UNKNOWN;
+ }
+
+ mLocalBluetoothAdapter = mLocalBluetoothManager.getBluetoothAdapter();
+ if (mLocalBluetoothAdapter == null) {
+ LOG.e("Error: there's a problem starting Bluetooth");
+ return REQUEST_UNKNOWN;
+ }
+
+ return request;
+ }
+
+ private final class StateChangeReceiver extends BroadcastReceiver {
+ private static final long TOGGLE_TIMEOUT_MILLIS = 10000; // 10 sec
+
+ StateChangeReceiver() {
+ getWindow().getDecorView().postDelayed(() -> {
+ if (!isFinishing() && !isDestroyed()) {
+ onCancel(null);
+ }
+ }, TOGGLE_TIMEOUT_MILLIS);
+ }
+
+ public void register() {
+ registerReceiver(/* receiver= */ this,
+ new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
+ }
+
+ public void unregister() {
+ unregisterReceiver(/* receiver= */ this);
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ Activity activity = BluetoothRequestPermissionActivity.this;
+ if (intent == null) {
+ return;
+ }
+ int currentState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+ BluetoothDevice.ERROR);
+ switch (mRequest) {
+ case REQUEST_ENABLE:
+ if (currentState == BluetoothAdapter.STATE_ON) {
+ activity.setResult(Activity.RESULT_OK);
+ proceedAndFinish();
+ }
+ break;
+
+ case REQUEST_DISABLE:
+ if (currentState == BluetoothAdapter.STATE_OFF) {
+ activity.setResult(Activity.RESULT_OK);
+ proceedAndFinish();
+ }
+ break;
+ }
+ }
+ }
+}