Add auto on toggle in settings.

Test: atest com.android.settings.bluetooth
Bug: b/316822488 b/316985153
Flag: ACONFIG com.android.settingslib.flags.bluetooth_qs_tile_dialog_auto_on_toggle DISABLED
Change-Id: Iaa8ce3d3f6e2ffa25d8b7a35b5f55f4774ac4a40
This commit is contained in:
chelseahao
2024-02-20 11:58:38 +08:00
parent e9917e2f48
commit 13461d2fb8
5 changed files with 303 additions and 17 deletions

View File

@@ -132,6 +132,8 @@
<string name="bluetooth_pairing_pref_title">Pair new device</string>
<!-- Keywords for bluetooth pairing item [CHAR LIMIT=30] -->
<string name="keywords_add_bt_device">bluetooth</string>
<!-- Title for bluetooth auto on toggle [CHAR LIMIT=60] -->
<string name="bluetooth_screen_auto_on_title">Automatically turn on again tomorrow</string>
<!-- Button to help user to pair right ear of the hearing aid device. It will show when only one of the hearing aid device set is connected. [CHAR LIMIT=20] -->
@@ -1800,8 +1802,12 @@
<string name="bluetooth_device_context_pair_connect">Pair &amp; connect</string>
<!-- Bluetooth settings. Text displayed when Bluetooth is off and device list is empty [CHAR LIMIT=NONE]-->
<string name="bluetooth_empty_list_bluetooth_off">When Bluetooth is turned on, your device can communicate with other nearby Bluetooth devices</string>
<!-- Bluetooth settings. Text displayed when Bluetooth is off and device list is empty when auto-on feature is enabled [CHAR LIMIT=NONE]-->
<string name="bluetooth_empty_list_bluetooth_off_auto_on_available">When Bluetooth is on, your device can communicate with other nearby Bluetooth devices. Features like Quick Share, Find My Device, and device location use Bluetooth.</string>
<!-- Bluetooth settings. Text displayed when Bluetooth is off and bluetooth scanning is turned on [CHAR LIMIT=NONE] -->
<string name="bluetooth_scanning_on_info_message">When Bluetooth is turned on, your device can communicate with other nearby Bluetooth devices.\n\nTo improve device experience, apps and services can still scan for nearby devices at any time, even when Bluetooth is off. This can be used, for example, to improve location-based features and services. You can change this in Bluetooth scanning settings.</string>
<!-- Bluetooth settings. Text displayed when Bluetooth is off and bluetooth scanning is turned on [CHAR LIMIT=NONE] -->
<string name="bluetooth_scanning_on_info_message_auto_on_available">When Bluetooth is on, your device can communicate with other nearby Bluetooth devices. Features like Quick Share, Find My Device, and device location use Bluetooth.\n\nApps and services can still scan for nearby devices at any time, even when Bluetooth is off. This can be used, for example, to improve location-based features and services. You can change this in Bluetooth scanning settings.</string>
<!-- Bluetooth settings. Link text to bring the user to "scanning settings" screen. [CHAR LIMIT=NONE]-->
<string name="bluetooth_scan_change">Change</string>

View File

@@ -18,6 +18,11 @@
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/bluetooth_settings_title">
<SwitchPreferenceCompat
android:key="bluetooth_auto_on_settings_toggle"
android:title="@string/bluetooth_screen_auto_on_title"
settings:controller="com.android.settings.bluetooth.BluetoothAutoOnPreferenceController"/>
<Preference
android:key="bluetooth_screen_bt_pair_rename_devices"
android:title="@string/bluetooth_device_name"

View File

@@ -0,0 +1,144 @@
/*
* Copyright (C) 2024 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.database.ContentObserver;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
import com.android.settings.core.TogglePreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
import com.android.settingslib.flags.Flags;
import com.android.settingslib.utils.ThreadUtils;
public class BluetoothAutoOnPreferenceController extends TogglePreferenceController
implements LifecycleObserver, OnStart, OnStop {
private static final String TAG = "BluetoothAutoOnPreferenceController";
@VisibleForTesting static final String PREF_KEY = "bluetooth_auto_on_settings_toggle";
static final String SETTING_NAME = "bluetooth_automatic_turn_on";
static final int UNSET = -1;
@VisibleForTesting static final int ENABLED = 1;
@VisibleForTesting static final int DISABLED = 0;
private final ContentObserver mContentObserver =
new ContentObserver(new Handler(/* async= */ true)) {
@Override
public void onChange(boolean selfChange) {
var unused =
ThreadUtils.postOnBackgroundThread(
() -> {
updateValue();
mContext.getMainExecutor()
.execute(
() -> {
if (mPreference != null) {
updateState(mPreference);
}
});
});
}
};
private int mAutoOnValue = UNSET;
@Nullable private TwoStatePreference mPreference;
public BluetoothAutoOnPreferenceController(
@NonNull Context context, @NonNull String preferenceKey) {
super(context, preferenceKey);
}
@Override
public void onStart() {
mContext.getContentResolver()
.registerContentObserver(
Settings.Secure.getUriFor(SETTING_NAME),
/* notifyForDescendants= */ false,
mContentObserver);
}
@Override
public void onStop() {
mContext.getContentResolver().unregisterContentObserver(mContentObserver);
}
@Override
public int getAvailabilityStatus() {
if (!Flags.bluetoothQsTileDialogAutoOnToggle()) {
return UNSUPPORTED_ON_DEVICE;
}
updateValue();
return mAutoOnValue != UNSET ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
@Override
public void displayPreference(@NonNull PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
}
@Override
public String getPreferenceKey() {
return PREF_KEY;
}
@Override
public boolean isChecked() {
return mAutoOnValue == ENABLED;
}
@Override
public boolean setChecked(boolean isChecked) {
if (getAvailabilityStatus() != AVAILABLE) {
Log.w(TAG, "Trying to set toggle value while feature not available.");
return false;
}
var unused =
ThreadUtils.postOnBackgroundThread(
() -> {
boolean updated =
Settings.Secure.putIntForUser(
mContext.getContentResolver(),
SETTING_NAME,
isChecked ? ENABLED : DISABLED,
UserHandle.myUserId());
if (updated) {
updateValue();
}
});
return true;
}
@Override
public int getSliceHighlightMenuRes() {
return 0;
}
private void updateValue() {
mAutoOnValue =
Settings.Secure.getIntForUser(
mContext.getContentResolver(), SETTING_NAME, UNSET, UserHandle.myUserId());
}
}

View File

@@ -15,8 +15,13 @@
*/
package com.android.settings.bluetooth;
import static com.android.settings.bluetooth.BluetoothAutoOnPreferenceController.SETTING_NAME;
import static com.android.settings.bluetooth.BluetoothAutoOnPreferenceController.UNSET;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.UserHandle;
import android.provider.Settings;
import android.view.View;
import androidx.annotation.VisibleForTesting;
@@ -29,6 +34,7 @@ import com.android.settings.widget.SwitchWidgetController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
import com.android.settingslib.flags.Flags;
import com.android.settingslib.widget.FooterPreference;
/**
@@ -36,8 +42,11 @@ import com.android.settingslib.widget.FooterPreference;
* is delegated to the SwitchWidgetController it uses.
*/
public class BluetoothSwitchPreferenceController
implements LifecycleObserver, OnStart, OnStop,
SwitchWidgetController.OnSwitchChangeListener, View.OnClickListener {
implements LifecycleObserver,
OnStart,
OnStop,
SwitchWidgetController.OnSwitchChangeListener,
View.OnClickListener {
private BluetoothEnabler mBluetoothEnabler;
private RestrictionUtils mRestrictionUtils;
@@ -46,18 +55,21 @@ public class BluetoothSwitchPreferenceController
private FooterPreference mFooterPreference;
private boolean mIsAlwaysDiscoverable;
@VisibleForTesting
AlwaysDiscoverable mAlwaysDiscoverable;
@VisibleForTesting AlwaysDiscoverable mAlwaysDiscoverable;
public BluetoothSwitchPreferenceController(Context context,
public BluetoothSwitchPreferenceController(
Context context,
SwitchWidgetController switchController,
FooterPreference footerPreference) {
this(context, new RestrictionUtils(), switchController, footerPreference);
}
@VisibleForTesting
public BluetoothSwitchPreferenceController(Context context, RestrictionUtils restrictionUtils,
SwitchWidgetController switchController, FooterPreference footerPreference) {
public BluetoothSwitchPreferenceController(
Context context,
RestrictionUtils restrictionUtils,
SwitchWidgetController switchController,
FooterPreference footerPreference) {
mRestrictionUtils = restrictionUtils;
mSwitch = switchController;
mContext = context;
@@ -66,11 +78,13 @@ public class BluetoothSwitchPreferenceController
mSwitch.setupView();
updateText(mSwitch.isChecked());
mBluetoothEnabler = new BluetoothEnabler(context,
switchController,
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(),
SettingsEnums.ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE,
mRestrictionUtils);
mBluetoothEnabler =
new BluetoothEnabler(
context,
switchController,
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(),
SettingsEnums.ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE,
mRestrictionUtils);
mBluetoothEnabler.setToggleCallback(this);
mAlwaysDiscoverable = new AlwaysDiscoverable(context);
}
@@ -97,8 +111,8 @@ public class BluetoothSwitchPreferenceController
/**
* Set whether the device can be discovered. By default the value will be {@code false}.
*
* @param isAlwaysDiscoverable {@code true} if the device can be discovered,
* otherwise {@code false}
* @param isAlwaysDiscoverable {@code true} if the device can be discovered, otherwise {@code
* false}
*/
public void setAlwaysDiscoverable(boolean isAlwaysDiscoverable) {
mIsAlwaysDiscoverable = isAlwaysDiscoverable;
@@ -119,15 +133,35 @@ public class BluetoothSwitchPreferenceController
.launch();
}
@VisibleForTesting void updateText(boolean isChecked) {
@VisibleForTesting
void updateText(boolean isChecked) {
if (!isChecked && Utils.isBluetoothScanningEnabled(mContext)) {
mFooterPreference.setTitle(R.string.bluetooth_scanning_on_info_message);
if (isAutoOnFeatureAvailable()) {
mFooterPreference.setTitle(
R.string.bluetooth_scanning_on_info_message_auto_on_available);
} else {
mFooterPreference.setTitle(R.string.bluetooth_scanning_on_info_message);
}
mFooterPreference.setLearnMoreText(mContext.getString(R.string.bluetooth_scan_change));
mFooterPreference.setLearnMoreAction(v -> onClick(v));
} else {
mFooterPreference.setTitle(R.string.bluetooth_empty_list_bluetooth_off);
if (isAutoOnFeatureAvailable()) {
mFooterPreference.setTitle(
R.string.bluetooth_empty_list_bluetooth_off_auto_on_available);
} else {
mFooterPreference.setTitle(R.string.bluetooth_empty_list_bluetooth_off);
}
mFooterPreference.setLearnMoreText("");
mFooterPreference.setLearnMoreAction(null);
}
}
private boolean isAutoOnFeatureAvailable() {
if (!Flags.bluetoothQsTileDialogAutoOnToggle()) {
return false;
}
return Settings.Secure.getIntForUser(
mContext.getContentResolver(), SETTING_NAME, UNSET, UserHandle.myUserId())
!= UNSET;
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2024 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 static com.android.settings.bluetooth.BluetoothAutoOnPreferenceController.DISABLED;
import static com.android.settings.bluetooth.BluetoothAutoOnPreferenceController.ENABLED;
import static com.android.settings.bluetooth.BluetoothAutoOnPreferenceController.PREF_KEY;
import static com.android.settings.bluetooth.BluetoothAutoOnPreferenceController.SETTING_NAME;
import static com.android.settings.bluetooth.BluetoothAutoOnPreferenceController.UNSET;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
import static com.android.settingslib.flags.Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
import android.content.ContentResolver;
import android.content.Context;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class BluetoothAutoOnPreferenceControllerTest {
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
private ContentResolver mContentResolver;
private BluetoothAutoOnPreferenceController mController;
@Before
public void setUp() {
mSetFlagsRule.enableFlags(FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE);
mContext = spy(ApplicationProvider.getApplicationContext());
mContentResolver = mContext.getContentResolver();
mController = new BluetoothAutoOnPreferenceController(mContext, PREF_KEY);
}
@Test
public void getAvailability_valueUnset_returnUnsupported() {
Settings.Secure.putInt(mContentResolver, SETTING_NAME, UNSET);
assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
}
@Test
public void getAvailability_valueSet_returnAvailable() {
Settings.Secure.putInt(mContentResolver, SETTING_NAME, DISABLED);
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
}
@Test
public void isChecked_valueEnabled_returnTrue() {
Settings.Secure.putInt(mContentResolver, SETTING_NAME, ENABLED);
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
assertThat(mController.isChecked()).isEqualTo(true);
}
@Test
public void setChecked_returnTrue() {
Settings.Secure.putInt(mContentResolver, SETTING_NAME, DISABLED);
mController.setChecked(true);
assertThat(mController.isChecked()).isEqualTo(true);
}
@Test
public void setChecked_returnFalse() {
Settings.Secure.putInt(mContentResolver, SETTING_NAME, ENABLED);
mController.setChecked(false);
assertThat(mController.isChecked()).isEqualTo(false);
}
}