diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 100a071483e..6aad2afc282 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2955,13 +2955,6 @@
-
-
-
{
+ onDisableConfirmed();
+ return Unit.INSTANCE;
+ },
+ () -> {
+ cancelAndFinish();
+ return Unit.INSTANCE;
+ });
} break;
default: {
@@ -126,18 +131,17 @@ public class RequestPermissionActivity extends Activity implements
* case via the broadcast receiver.
*/
- /*
- * Start the helper activity to:
- * 1) ask the user about enabling bt AND discovery
- * 2) enable BT upon confirmation
- */
- Intent intent = new Intent(this, RequestPermissionHelperActivity.class);
- intent.setAction(RequestPermissionHelperActivity.ACTION_INTERNAL_REQUEST_BT_ON);
- intent.putExtra(RequestPermissionHelperActivity.EXTRA_APP_LABEL, mAppLabel);
- if (mRequest == REQUEST_ENABLE_DISCOVERABLE) {
- intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, mTimeout);
- }
- startActivityForResult(intent, 0);
+ // Start the helper activity to ask the user about enabling bt AND discovery
+ RequestPermissionHelper.INSTANCE.requestEnable(this, mAppLabel,
+ mRequest == REQUEST_ENABLE_DISCOVERABLE ? mTimeout : -1,
+ () -> {
+ onEnableConfirmed();
+ return Unit.INSTANCE;
+ },
+ () -> {
+ cancelAndFinish();
+ return Unit.INSTANCE;
+ });
} break;
case BluetoothAdapter.STATE_ON: {
@@ -202,42 +206,27 @@ public class RequestPermissionActivity extends Activity implements
mDialog.show();
}
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (resultCode != Activity.RESULT_OK) {
- cancelAndFinish();
- return;
+ private void onEnableConfirmed() {
+ mBluetoothAdapter.enable();
+ if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
+ proceedAndFinish();
+ } else {
+ // If BT is not up yet, show "Turning on Bluetooth..."
+ mReceiver = new StateChangeReceiver();
+ registerReceiver(mReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
+ createDialog();
}
+ }
- switch (mRequest) {
- case REQUEST_ENABLE:
- case REQUEST_ENABLE_DISCOVERABLE: {
- if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
- proceedAndFinish();
- } else {
- // If BT is not up yet, show "Turning on Bluetooth..."
- mReceiver = new StateChangeReceiver();
- registerReceiver(mReceiver, new IntentFilter(
- BluetoothAdapter.ACTION_STATE_CHANGED));
- createDialog();
- }
- } break;
-
- case REQUEST_DISABLE: {
- if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF) {
- proceedAndFinish();
- } else {
- // If BT is not up yet, show "Turning off Bluetooth..."
- mReceiver = new StateChangeReceiver();
- registerReceiver(mReceiver, new IntentFilter(
- BluetoothAdapter.ACTION_STATE_CHANGED));
- createDialog();
- }
- } break;
-
- default: {
- cancelAndFinish();
- } break;
+ private void onDisableConfirmed() {
+ mBluetoothAdapter.disable();
+ if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF) {
+ proceedAndFinish();
+ } else {
+ // If BT is not up yet, show "Turning off Bluetooth..."
+ mReceiver = new StateChangeReceiver();
+ registerReceiver(mReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
+ createDialog();
}
}
diff --git a/src/com/android/settings/bluetooth/RequestPermissionHelper.kt b/src/com/android/settings/bluetooth/RequestPermissionHelper.kt
new file mode 100644
index 00000000000..000a7d16295
--- /dev/null
+++ b/src/com/android/settings/bluetooth/RequestPermissionHelper.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 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.settings.bluetooth
+
+import android.content.Context
+import android.os.UserManager
+import androidx.appcompat.app.AlertDialog
+import com.android.settings.R
+import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
+import com.android.settingslib.spaprivileged.framework.common.userManager
+
+object RequestPermissionHelper {
+ fun requestEnable(
+ context: Context,
+ appLabel: CharSequence?,
+ timeout: Int,
+ onAllow: () -> Unit,
+ onDeny: () -> Unit,
+ ) {
+ if (context.resources.getBoolean(R.bool.auto_confirm_bluetooth_activation_dialog)) {
+ // Don't even show the dialog if configured this way
+ onAllow()
+ return
+ }
+ AlertDialog.Builder(context).apply {
+ setMessage(context.getEnableMessage(timeout, appLabel))
+ setPositiveButton(R.string.allow) { _, _ ->
+ if (context.isDisallowBluetooth()) onDeny() else onAllow()
+ }
+ setNegativeButton(R.string.deny) { _, _ -> onDeny() }
+ setOnCancelListener { onDeny() }
+ }.show()
+ }
+
+ fun requestDisable(
+ context: Context,
+ appLabel: CharSequence?,
+ onAllow: () -> Unit,
+ onDeny: () -> Unit,
+ ) {
+ if (context.resources.getBoolean(R.bool.auto_confirm_bluetooth_activation_dialog)) {
+ // Don't even show the dialog if configured this way
+ onAllow()
+ return
+ }
+ AlertDialog.Builder(context).apply {
+ setMessage(context.getDisableMessage(appLabel))
+ setPositiveButton(R.string.allow) { _, _ -> onAllow() }
+ setNegativeButton(R.string.deny) { _, _ -> onDeny() }
+ setOnCancelListener { onDeny() }
+ }.show()
+ }
+}
+
+// If Bluetooth is disallowed, don't try to enable it, show policy transparency message instead.
+private fun Context.isDisallowBluetooth() =
+ if (userManager.hasUserRestriction(UserManager.DISALLOW_BLUETOOTH)) {
+ devicePolicyManager.createAdminSupportIntent(UserManager.DISALLOW_BLUETOOTH)
+ ?.let { startActivity(it) }
+ true
+ } else false
+
+private fun Context.getEnableMessage(timeout: Int, name: CharSequence?): String = when {
+ timeout < 0 -> when (name) {
+ null -> getString(R.string.bluetooth_ask_enablement_no_name)
+ else -> getString(R.string.bluetooth_ask_enablement, name)
+ }
+
+ timeout == BluetoothDiscoverableEnabler.DISCOVERABLE_TIMEOUT_NEVER -> when (name) {
+ null -> getString(R.string.bluetooth_ask_enablement_and_lasting_discovery_no_name)
+ else -> getString(R.string.bluetooth_ask_enablement_and_lasting_discovery, name)
+ }
+
+ else -> when (name) {
+ null -> getString(R.string.bluetooth_ask_enablement_and_discovery_no_name, timeout)
+ else -> getString(R.string.bluetooth_ask_enablement_and_discovery, name, timeout)
+ }
+}
+
+private fun Context.getDisableMessage(name: CharSequence?): String =
+ when (name) {
+ null -> getString(R.string.bluetooth_ask_disablement_no_name)
+ else -> getString(R.string.bluetooth_ask_disablement, name)
+ }
diff --git a/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java b/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java
deleted file mode 100644
index 99ac9281510..00000000000
--- a/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2009 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.settings.bluetooth;
-
-import android.app.Activity;
-import android.app.admin.DevicePolicyManager;
-import android.bluetooth.BluetoothAdapter;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.UserManager;
-import android.util.Log;
-
-import com.android.internal.app.AlertActivity;
-import com.android.internal.app.AlertController;
-import com.android.settings.R;
-
-/**
- * RequestPermissionHelperActivity asks the user whether to toggle Bluetooth.
- *
- * TODO: This activity isn't needed - this should be folded in RequestPermissionActivity
- */
-public class RequestPermissionHelperActivity extends AlertActivity implements
- DialogInterface.OnClickListener {
- private static final String TAG = "RequestPermissionHelperActivity";
-
- public static final String ACTION_INTERNAL_REQUEST_BT_ON =
- "com.android.settings.bluetooth.ACTION_INTERNAL_REQUEST_BT_ON";
-
- public static final String ACTION_INTERNAL_REQUEST_BT_OFF =
- "com.android.settings.bluetooth.ACTION_INTERNAL_REQUEST_BT_OFF";
-
- public static final String EXTRA_APP_LABEL =
- "com.android.settings.bluetooth.extra.APP_LABEL";
-
- private BluetoothAdapter mBluetoothAdapter;
-
- private CharSequence mAppLabel;
-
- private int mTimeout = -1;
-
- private int mRequest;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setResult(RESULT_CANCELED);
-
- // Note: initializes mBluetoothAdapter and returns true on error
- if (!parseIntent()) {
- finish();
- return;
- }
-
- if (getResources().getBoolean(R.bool.auto_confirm_bluetooth_activation_dialog)) {
- // Don't even show the dialog if configured this way
- onClick(null, BUTTON_POSITIVE);
- dismiss();
- }
-
- createDialog();
- }
-
- void createDialog() {
- final AlertController.AlertParams p = mAlertParams;
-
- switch (mRequest) {
- case RequestPermissionActivity.REQUEST_ENABLE: {
- if (mTimeout < 0) {
- p.mMessage = mAppLabel != null
- ? getString(R.string.bluetooth_ask_enablement, mAppLabel)
- : getString(R.string.bluetooth_ask_enablement_no_name);
- } else if (mTimeout == BluetoothDiscoverableEnabler.DISCOVERABLE_TIMEOUT_NEVER) {
- p.mMessage = mAppLabel != null
- ? getString(
- R.string.bluetooth_ask_enablement_and_lasting_discovery,
- mAppLabel)
- : getString(
- R.string.bluetooth_ask_enablement_and_lasting_discovery_no_name);
- } else {
- p.mMessage = mAppLabel != null
- ? getString(R.string.bluetooth_ask_enablement_and_discovery,
- mAppLabel, mTimeout)
- : getString(R.string.bluetooth_ask_enablement_and_discovery_no_name,
- mTimeout);
- }
- } break;
-
- case RequestPermissionActivity.REQUEST_DISABLE: {
- p.mMessage = mAppLabel != null
- ? getString(R.string.bluetooth_ask_disablement, mAppLabel)
- : getString(R.string.bluetooth_ask_disablement_no_name);
- } break;
- }
-
- p.mPositiveButtonText = getString(R.string.allow);
- p.mPositiveButtonListener = this;
- p.mNegativeButtonText = getString(R.string.deny);
-
- setupAlert();
- }
-
- public void onClick(DialogInterface dialog, int which) {
- switch (mRequest) {
- case RequestPermissionActivity.REQUEST_ENABLE:
- case RequestPermissionActivity.REQUEST_ENABLE_DISCOVERABLE: {
- 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);
- }
- } else {
- mBluetoothAdapter.enable();
- setResult(Activity.RESULT_OK);
- }
- } break;
-
- case RequestPermissionActivity.REQUEST_DISABLE: {
- mBluetoothAdapter.disable();
- setResult(Activity.RESULT_OK);
- } break;
- }
- }
-
- /**
- * Parse the received Intent and initialize mBluetoothAdapter.
- * @return true if an error occurred; false otherwise
- */
- private boolean parseIntent() {
- Intent intent = getIntent();
- if (intent == null) {
- return false;
- }
-
- String action = intent.getAction();
- if (ACTION_INTERNAL_REQUEST_BT_ON.equals(action)) {
- mRequest = RequestPermissionActivity.REQUEST_ENABLE;
- if (intent.hasExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION)) {
- // Value used for display purposes. Not range checking.
- mTimeout = intent.getIntExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,
- BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT);
- }
- } else if (ACTION_INTERNAL_REQUEST_BT_OFF.equals(action)) {
- mRequest = RequestPermissionActivity.REQUEST_DISABLE;
- } else {
- return false;
- }
-
- mAppLabel = getIntent().getCharSequenceExtra(EXTRA_APP_LABEL);
-
- mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- if (mBluetoothAdapter == null) {
- Log.e(TAG, "Error: there's a problem starting Bluetooth");
- return false;
- }
-
- return true;
- }
-}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/RequestPermissionHelperTest.kt b/tests/robotests/src/com/android/settings/bluetooth/RequestPermissionHelperTest.kt
new file mode 100644
index 00000000000..ce0792c468a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/RequestPermissionHelperTest.kt
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2023 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.settings.bluetooth
+
+import androidx.activity.ComponentActivity
+import com.android.settings.R
+import com.android.settings.testutils.shadow.ShadowAlertDialogCompat
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.spy
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.android.controller.ActivityController
+import org.robolectric.annotation.Config
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(RobolectricTestRunner::class)
+@Config(shadows = [ShadowAlertDialogCompat::class])
+class RequestPermissionHelperTest {
+ private lateinit var activityController: ActivityController
+
+ @Before
+ fun setUp() {
+ activityController =
+ ActivityController.of(ComponentActivity()).create().start().postCreate(null).resume()
+ }
+
+ @After
+ fun tearDown() {
+ activityController.pause().stop().destroy()
+ }
+
+ @Test
+ fun requestEnable_withAppLabelAndNoTimeout_hasCorrectMessage() {
+ val activity = activityController.get()
+ RequestPermissionHelper.requestEnable(
+ context = activity,
+ appLabel = "App Label",
+ timeout = -1,
+ onAllow = {},
+ onDeny = {},
+ )
+
+ assertLatestMessageIs("App Label wants to turn on Bluetooth")
+ }
+
+ @Test
+ fun requestEnable_withAppLabelAndZeroTimeout_hasCorrectMessage() {
+ val activity = activityController.get()
+ RequestPermissionHelper.requestEnable(
+ context = activity,
+ appLabel = "App Label",
+ timeout = 0,
+ onAllow = {},
+ onDeny = {},
+ )
+
+ assertLatestMessageIs(
+ "App Label wants to turn on Bluetooth and make your phone visible to other devices. " +
+ "You can change this later in Bluetooth settings."
+ )
+ }
+
+ @Test
+ fun requestEnable_withAppLabelAndNormalTimeout_hasCorrectMessage() {
+ val activity = activityController.get()
+ RequestPermissionHelper.requestEnable(
+ context = activity,
+ appLabel = "App Label",
+ timeout = 120,
+ onAllow = {},
+ onDeny = {},
+ )
+
+ assertLatestMessageIs(
+ "App Label wants to turn on Bluetooth and make your phone visible to other devices " +
+ "for 120 seconds."
+ )
+ }
+
+ @Test
+ fun requestEnable_withNoAppLabelAndNoTimeout_hasCorrectMessage() {
+ val activity = activityController.get()
+ RequestPermissionHelper.requestEnable(
+ context = activity,
+ appLabel = null,
+ timeout = -1,
+ onAllow = {},
+ onDeny = {},
+ )
+
+ assertLatestMessageIs("An app wants to turn on Bluetooth")
+ }
+
+ @Test
+ fun requestEnable_withNoAppLabelAndZeroTimeout_hasCorrectMessage() {
+ val activity = activityController.get()
+ RequestPermissionHelper.requestEnable(
+ context = activity,
+ appLabel = null,
+ timeout = 0,
+ onAllow = {},
+ onDeny = {},
+ )
+
+ assertLatestMessageIs(
+ "An app wants to turn on Bluetooth and make your phone visible to other devices. " +
+ "You can change this later in Bluetooth settings."
+ )
+ }
+
+ @Test
+ fun requestEnable_withNoAppLabelAndNormalTimeout_hasCorrectMessage() {
+ val activity = activityController.get()
+ RequestPermissionHelper.requestEnable(
+ context = activity,
+ appLabel = null,
+ timeout = 120,
+ onAllow = {},
+ onDeny = {},
+ )
+
+ assertLatestMessageIs(
+ "An app wants to turn on Bluetooth and make your phone visible to other devices for " +
+ "120 seconds."
+ )
+ }
+
+ @Test
+ fun requestEnable_whenAutoConfirm_onAllowIsCalled() {
+ val activity = getActivityWith(autoConfirm = true)
+ var onAllowCalled = false
+
+ RequestPermissionHelper.requestEnable(
+ context = activity,
+ appLabel = null,
+ timeout = -1,
+ onAllow = { onAllowCalled = true },
+ onDeny = {},
+ )
+
+ assertThat(onAllowCalled).isTrue()
+ }
+
+ @Test
+ fun requestEnable_whenNotAutoConfirm_onAllowIsNotCalledWhenRequest() {
+ val activity = getActivityWith(autoConfirm = false)
+ var onAllowCalled = false
+
+ RequestPermissionHelper.requestEnable(
+ context = activity,
+ appLabel = null,
+ timeout = -1,
+ onAllow = { onAllowCalled = true },
+ onDeny = {},
+ )
+
+ assertThat(onAllowCalled).isFalse()
+ }
+
+ @Test
+ fun requestDisable_withAppLabel_hasCorrectMessage() {
+ val activity = activityController.get()
+ RequestPermissionHelper.requestDisable(
+ context = activity,
+ appLabel = "App Label",
+ onAllow = {},
+ onDeny = {},
+ )
+
+ assertLatestMessageIs("App Label wants to turn off Bluetooth")
+ }
+
+ @Test
+ fun requestDisable_withNoAppLabel_hasCorrectMessage() {
+ val activity = activityController.get()
+ RequestPermissionHelper.requestDisable(
+ context = activity,
+ appLabel = null,
+ onAllow = {},
+ onDeny = {},
+ )
+
+ assertLatestMessageIs("An app wants to turn off Bluetooth")
+ }
+
+ @Test
+ fun requestDisable_whenAutoConfirm_onAllowIsCalled() {
+ val activity = getActivityWith(autoConfirm = true)
+ var onAllowCalled = false
+
+ RequestPermissionHelper.requestDisable(
+ context = activity,
+ appLabel = null,
+ onAllow = { onAllowCalled = true },
+ onDeny = {},
+ )
+
+ assertThat(onAllowCalled).isTrue()
+ }
+
+ @Test
+ fun requestDisable_whenNotAutoConfirm_onAllowIsNotCalledWhenRequest() {
+ val activity = getActivityWith(autoConfirm = false)
+ var onAllowCalled = false
+
+ RequestPermissionHelper.requestDisable(
+ context = activity,
+ appLabel = null,
+ onAllow = { onAllowCalled = true },
+ onDeny = {},
+ )
+
+ assertThat(onAllowCalled).isFalse()
+ }
+
+ private fun getActivityWith(autoConfirm: Boolean): ComponentActivity {
+ val activity = spy(activityController.get())
+ val spyResources = spy(activity.resources)
+ whenever(activity.resources).thenReturn(spyResources)
+ whenever(spyResources.getBoolean(R.bool.auto_confirm_bluetooth_activation_dialog))
+ .thenReturn(autoConfirm)
+ return activity
+ }
+
+ private fun assertLatestMessageIs(message: String) {
+ val dialog = ShadowAlertDialogCompat.getLatestAlertDialog()
+ val shadowDialog = ShadowAlertDialogCompat.shadowOf(dialog)
+ assertThat(shadowDialog.message.toString()).isEqualTo(message)
+ }
+}