From b5dbe8af2b0e0435a2158fc2826ecdaada601f54 Mon Sep 17 00:00:00 2001 From: Angela Wang Date: Fri, 22 Nov 2024 06:21:10 +0000 Subject: [PATCH 01/10] Show battery info for mono audio location device 1. Show mono battery info in LeAudioBluetoothDetailsHeaderController without side text in front of the battery icon 2. Show mono side device on Settings > Accessibility > Hearing devices's summary without side information Flag: EXEMPT bugfix Bug: 379616650 Test: atest AccessibilityHearingAidPreferenceControllerTest Test: manually check UI with real device Change-Id: I4a1a3357e2cef51df505923e38da33767c78e8f3 --- res/layout/le_audio_bt_entity_header.xml | 18 ++++++++++++++++++ ...ibilityHearingAidPreferenceController.java | 7 +------ ...AudioBluetoothDetailsHeaderController.java | 11 ++++++----- ...ityHearingAidPreferenceControllerTest.java | 19 ++++++++++++++++++- 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/res/layout/le_audio_bt_entity_header.xml b/res/layout/le_audio_bt_entity_header.xml index 81911e9c946..421ee393fea 100644 --- a/res/layout/le_audio_bt_entity_header.xml +++ b/res/layout/le_audio_bt_entity_header.xml @@ -176,5 +176,23 @@ android:padding="@dimen/le_bluetooth_summary_padding" android:drawablePadding="@dimen/le_bluetooth_summary_drawable_padding"/> + + + diff --git a/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java index 2546d44d8a6..52560c98eb4 100644 --- a/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java +++ b/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java @@ -162,18 +162,13 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC } final int side = device.getDeviceSide(); - if (side == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) { - return mContext.getString( - R.string.accessibility_hearingaid_left_and_right_side_device_summary, name); - } else if (side == HearingAidInfo.DeviceSide.SIDE_LEFT) { + if (side == HearingAidInfo.DeviceSide.SIDE_LEFT) { return mContext.getString( R.string.accessibility_hearingaid_left_side_device_summary, name); } else if (side == HearingAidInfo.DeviceSide.SIDE_RIGHT) { return mContext.getString( R.string.accessibility_hearingaid_right_side_device_summary, name); } - - // Invalid side return mContext.getString( R.string.accessibility_hearingaid_active_device_summary, name); } diff --git a/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java index 25248942be7..4c7c98eb48f 100644 --- a/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java +++ b/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java @@ -225,6 +225,8 @@ public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceContr return R.id.bt_battery_left_summary; } else if (containerId == R.id.bt_battery_right) { return R.id.bt_battery_right_summary; + } else if (containerId == R.id.bt_battery_mono) { + return R.id.bt_battery_mono_summary; } Log.d(TAG, "No summary resource id. The containerId is " + containerId); return INVALID_RESOURCE_ID; @@ -237,6 +239,8 @@ public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceContr updateBatteryLayout(R.id.bt_battery_left, BluetoothUtils.META_INT_ERROR); // hide the right updateBatteryLayout(R.id.bt_battery_right, BluetoothUtils.META_INT_ERROR); + // hide the mono + updateBatteryLayout(R.id.bt_battery_mono, BluetoothUtils.META_INT_ERROR); } private void updateBatteryLayout() { @@ -261,11 +265,6 @@ public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceContr int deviceId = leAudioProfile.getAudioLocation(cachedDevice.getDevice()); Log.d(TAG, "LeAudioDevices:" + cachedDevice.getDevice().getAnonymizedAddress() + ", deviceId:" + deviceId); - - if (deviceId == BluetoothLeAudio.AUDIO_LOCATION_INVALID) { - Log.d(TAG, "The device does not support the AUDIO_LOCATION."); - return; - } boolean isLeft = (deviceId & LEFT_DEVICE_ID) != 0; boolean isRight = (deviceId & RIGHT_DEVICE_ID) != 0; boolean isLeftRight = isLeft && isRight; @@ -280,6 +279,8 @@ public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceContr updateBatteryLayout(R.id.bt_battery_left, cachedDevice.getBatteryLevel()); } else if (isRight) { updateBatteryLayout(R.id.bt_battery_right, cachedDevice.getBatteryLevel()); + } else if (deviceId == BluetoothLeAudio.AUDIO_LOCATION_MONO) { + updateBatteryLayout(R.id.bt_battery_mono, cachedDevice.getBatteryLevel()); } else { Log.d(TAG, "The device id is other Audio Location. Do nothing."); } diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java index 97faa63400e..b8b0699e8c7 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java @@ -208,7 +208,24 @@ public class AccessibilityHearingAidPreferenceControllerTest { ShadowLooper.idleMainLooper(); assertThat(mHearingAidPreference.getSummary().toString().contentEquals( - "TEST_HEARING_AID_BT_DEVICE_NAME / Left and right")).isTrue(); + "TEST_HEARING_AID_BT_DEVICE_NAME active")).isTrue(); + } + + @Test + public void getSummary_connectedLeAudioHearingAidMonoSide_connectedSummary() { + when(mCachedBluetoothDevice.getDeviceSide()).thenReturn( + HearingAidInfo.DeviceSide.SIDE_MONO); + when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(new HashSet<>()); + when(mHapClientProfile.getConnectedDevices()).thenReturn(generateHearingAidDeviceList()); + + mPreferenceController.onStart(); + Intent intent = new Intent(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED); + intent.putExtra(BluetoothHearingAid.EXTRA_STATE, BluetoothHapClient.STATE_CONNECTED); + sendIntent(intent); + ShadowLooper.idleMainLooper(); + + assertThat(mHearingAidPreference.getSummary().toString().contentEquals( + "TEST_HEARING_AID_BT_DEVICE_NAME active")).isTrue(); } @Test From 69461087982077b7af5a65268580a4c3b4db9ee9 Mon Sep 17 00:00:00 2001 From: Yining Liu Date: Thu, 5 Dec 2024 23:32:13 +0000 Subject: [PATCH 02/10] Use the new logging event for "Notifications on lock screen" in Settings Bug: 367455695 Change-Id: I7310e625d2415dbad5da25d4c1b02e359f169010 Test: Build Flag: com.android.server.notification.notification_lock_screen_settings --- .../LockScreenNotificationsPreferencePageFragment.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/com/android/settings/notification/LockScreenNotificationsPreferencePageFragment.java b/src/com/android/settings/notification/LockScreenNotificationsPreferencePageFragment.java index ef53e2da829..53b1377d3ea 100644 --- a/src/com/android/settings/notification/LockScreenNotificationsPreferencePageFragment.java +++ b/src/com/android/settings/notification/LockScreenNotificationsPreferencePageFragment.java @@ -33,8 +33,7 @@ public class LockScreenNotificationsPreferencePageFragment extends DashboardFrag @Override public int getMetricsCategory() { - //TODO(b/367455695): create a new metrics category - return SettingsEnums.SETTINGS_LOCK_SCREEN_PREFERENCES; + return SettingsEnums.SETTINGS_NOTIFICATIONS_ON_LOCK_SCREEN; } @Override From f79173a66499a7d53081a6e415bcf1433426b976 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Mon, 9 Dec 2024 16:59:16 +0800 Subject: [PATCH 03/10] [Catalyst] Support callChangeListener for MainSwitchBarPreference Bug: 335132588 Flag: EXEMPT library Test: manual Change-Id: I6924ccfa0d66b9a643811f49a2ec3fcdc1d749ae --- src/com/android/settings/widget/MainSwitchBarPreference.kt | 6 +++++- src/com/android/settings/widget/SettingsMainSwitchBar.java | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/widget/MainSwitchBarPreference.kt b/src/com/android/settings/widget/MainSwitchBarPreference.kt index 6ed887790c8..5cf96397184 100644 --- a/src/com/android/settings/widget/MainSwitchBarPreference.kt +++ b/src/com/android/settings/widget/MainSwitchBarPreference.kt @@ -26,7 +26,7 @@ import com.android.settingslib.widget.MainSwitchBar /** Preference abstraction of the [MainSwitchBar] in settings activity. */ class MainSwitchBarPreference(context: Context, private val metadata: MainSwitchBarMetadata) : - TwoStatePreference(context), OnCheckedChangeListener { + TwoStatePreference(context), OnCheckedChangeListener, MainSwitchBar.PreChangeListener { private val mainSwitchBar: MainSwitchBar = (context as SettingsActivity).switchBar @@ -62,9 +62,12 @@ class MainSwitchBarPreference(context: Context, private val metadata: MainSwitch override fun onAttached() { super.onAttached() + mainSwitchBar.setPreChangeListener(this) mainSwitchBar.addOnSwitchChangeListener(this) } + override fun preChange(isCheck: Boolean) = callChangeListener(isCheck) + override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { // prevent user from toggling the switch before data store operation is done isEnabled = false @@ -74,6 +77,7 @@ class MainSwitchBarPreference(context: Context, private val metadata: MainSwitch override fun onDetached() { mainSwitchBar.removeOnSwitchChangeListener(this) + mainSwitchBar.setPreChangeListener(null) super.onDetached() } } diff --git a/src/com/android/settings/widget/SettingsMainSwitchBar.java b/src/com/android/settings/widget/SettingsMainSwitchBar.java index e8c6fc9b0b9..6bccbd7205d 100644 --- a/src/com/android/settings/widget/SettingsMainSwitchBar.java +++ b/src/com/android/settings/widget/SettingsMainSwitchBar.java @@ -109,7 +109,7 @@ public class SettingsMainSwitchBar extends MainSwitchBar { return true; } - return mSwitch.performClick(); + return callPreChangeListener() && mSwitch.performClick(); } @Override From 95719f21b32c99383fd643acfcb61656eee6812f Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Mon, 9 Dec 2024 17:19:40 +0800 Subject: [PATCH 04/10] [Catalyst] Add SatelliteRepository.isSatelliteOn helper method NO_IFTTT=Catalyst only Bug: 335132588 Flag: EXEMPT refactor Test: manual Change-Id: Ib10f7f616a6deed438a129da4b2cb927915bfcda --- .../network/AirplaneModePreference.kt | 18 +---------------- .../network/NetworkDashboardScreen.kt | 6 ++++++ .../settings/network/SatelliteRepository.kt | 12 +++++++++++ .../settings/wifi/WifiSwitchPreference.kt | 20 +++---------------- 4 files changed, 22 insertions(+), 34 deletions(-) diff --git a/src/com/android/settings/network/AirplaneModePreference.kt b/src/com/android/settings/network/AirplaneModePreference.kt index 758bcfbe487..e3b7f5503d6 100644 --- a/src/com/android/settings/network/AirplaneModePreference.kt +++ b/src/com/android/settings/network/AirplaneModePreference.kt @@ -26,13 +26,13 @@ import android.os.UserManager import android.provider.Settings import android.telephony.PhoneStateListener import android.telephony.TelephonyManager -import android.util.Log import androidx.annotation.DrawableRes import androidx.preference.Preference import com.android.settings.AirplaneModeEnabler import com.android.settings.PreferenceRestrictionMixin import com.android.settings.R import com.android.settings.Utils +import com.android.settings.network.SatelliteRepository.Companion.isSatelliteOn import com.android.settingslib.RestrictedSwitchPreference import com.android.settingslib.datastore.AbstractKeyedDataObservable import com.android.settingslib.datastore.DataChangeReason @@ -45,8 +45,6 @@ import com.android.settingslib.metadata.PreferenceLifecycleProvider import com.android.settingslib.metadata.ReadWritePermit import com.android.settingslib.metadata.SensitivityLevel import com.android.settingslib.metadata.SwitchPreference -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit // LINT.IfChange class AirplaneModePreference : @@ -113,9 +111,7 @@ class AirplaneModePreference : context.getSystemService(TelephonyManager::class.java)?.let { phoneStateListener = object : PhoneStateListener(Looper.getMainLooper()) { - @Deprecated("Deprecated in Java") override fun onRadioPowerStateChanged(state: Int) { - Log.d(TAG, "onRadioPowerStateChanged(), state=$state") notifyChange(KEY, DataChangeReason.UPDATE) } } @@ -163,17 +159,6 @@ class AirplaneModePreference : context.getSystemService(TelephonyManager::class.java), ) - private fun isSatelliteOn(context: Context): Boolean { - try { - return SatelliteRepository(context) - .requestIsSessionStarted(Executors.newSingleThreadExecutor()) - .get(2000, TimeUnit.MILLISECONDS) - } catch (e: Exception) { - Log.e(TAG, "Error to get satellite status : $e") - } - return false - } - private fun showEcmDialog(context: PreferenceLifecycleContext) { val intent = Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null) @@ -192,7 +177,6 @@ class AirplaneModePreference : } companion object { - const val TAG = "AirplaneModePreference" const val KEY = Settings.Global.AIRPLANE_MODE_ON const val DEFAULT_VALUE = false const val REQUEST_CODE_EXIT_ECM = 1 diff --git a/src/com/android/settings/network/NetworkDashboardScreen.kt b/src/com/android/settings/network/NetworkDashboardScreen.kt index 15bf590f462..1ed88c080d1 100644 --- a/src/com/android/settings/network/NetworkDashboardScreen.kt +++ b/src/com/android/settings/network/NetworkDashboardScreen.kt @@ -17,9 +17,12 @@ package com.android.settings.network import android.content.Context import com.android.settings.R +import com.android.settings.Settings.NetworkDashboardActivity import com.android.settings.datausage.DataSaverScreen import com.android.settings.flags.Flags +import com.android.settings.utils.makeLaunchIntent import com.android.settingslib.metadata.PreferenceIconProvider +import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.ProvidePreferenceScreen import com.android.settingslib.metadata.preferenceHierarchy import com.android.settingslib.preference.PreferenceScreenCreator @@ -44,6 +47,9 @@ class NetworkDashboardScreen : PreferenceScreenCreator, PreferenceIconProvider { override fun fragmentClass() = NetworkDashboardFragment::class.java + override fun getLaunchIntent(context: Context, metadata: PreferenceMetadata?) = + makeLaunchIntent(context, NetworkDashboardActivity::class.java, metadata?.key) + override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) { +MobileNetworkListScreen.KEY order -15 diff --git a/src/com/android/settings/network/SatelliteRepository.kt b/src/com/android/settings/network/SatelliteRepository.kt index b7c25f4f658..c70484a7e7a 100644 --- a/src/com/android/settings/network/SatelliteRepository.kt +++ b/src/com/android/settings/network/SatelliteRepository.kt @@ -26,6 +26,8 @@ import androidx.concurrent.futures.CallbackToFutureAdapter import com.google.common.util.concurrent.Futures.immediateFuture import com.google.common.util.concurrent.ListenableFuture import java.util.concurrent.Executor +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor @@ -203,5 +205,15 @@ class SatelliteRepository( fun setIsSessionStartedForTesting(isEnabled: Boolean) { this.isSessionStarted = isEnabled } + + fun isSatelliteOn(context: Context, timeoutMs: Long = 2000): Boolean = + try { + SatelliteRepository(context) + .requestIsSessionStarted(Executors.newSingleThreadExecutor()) + .get(timeoutMs, TimeUnit.MILLISECONDS) + } catch (e: Exception) { + Log.e(TAG, "Error to get satellite status : $e") + false + } } } diff --git a/src/com/android/settings/wifi/WifiSwitchPreference.kt b/src/com/android/settings/wifi/WifiSwitchPreference.kt index 2a18d3f5616..ba6fb02b769 100644 --- a/src/com/android/settings/wifi/WifiSwitchPreference.kt +++ b/src/com/android/settings/wifi/WifiSwitchPreference.kt @@ -23,13 +23,12 @@ import android.content.IntentFilter import android.net.wifi.WifiManager import android.os.UserManager import android.provider.Settings -import android.util.Log import android.widget.Toast import androidx.preference.Preference import androidx.preference.Preference.OnPreferenceChangeListener import com.android.settings.PreferenceRestrictionMixin import com.android.settings.R -import com.android.settings.network.SatelliteRepository +import com.android.settings.network.SatelliteRepository.Companion.isSatelliteOn import com.android.settings.network.SatelliteWarningDialogActivity import com.android.settingslib.RestrictedSwitchPreference import com.android.settingslib.WirelessUtils @@ -42,8 +41,6 @@ import com.android.settingslib.metadata.ReadWritePermit import com.android.settingslib.metadata.SensitivityLevel import com.android.settingslib.metadata.SwitchPreference import com.android.settingslib.preference.SwitchPreferenceBinding -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit // LINT.IfChange class WifiSwitchPreference : @@ -75,7 +72,7 @@ class WifiSwitchPreference : val context = preference.context // Show dialog and do nothing under satellite mode. - if (context.isSatelliteOn()) { + if (isSatelliteOn(context)) { context.startActivity( Intent(context, SatelliteWarningDialogActivity::class.java) .putExtra( @@ -100,7 +97,7 @@ class WifiSwitchPreference : override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = when { - (value == true && !context.isRadioAllowed()) || context.isSatelliteOn() -> + (value == true && !context.isRadioAllowed()) || isSatelliteOn(context) -> ReadWritePermit.DISALLOW else -> ReadWritePermit.ALLOW } @@ -155,22 +152,11 @@ class WifiSwitchPreference : } companion object { - const val TAG = "WifiSwitchPreference" const val KEY = "main_toggle_wifi" private fun Context.isRadioAllowed() = WirelessUtils.isRadioAllowed(this, Settings.Global.RADIO_WIFI) - private fun Context.isSatelliteOn() = - try { - SatelliteRepository(this) - .requestIsSessionStarted(Executors.newSingleThreadExecutor()) - .get(2000, TimeUnit.MILLISECONDS) - } catch (e: Exception) { - Log.e(TAG, "Error to get satellite status : $e") - false - } - private val Intent.wifiState get() = getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN) } From 2b21d69c0cf66bc32b1c369a1f19df0bb7fba686 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Mon, 9 Dec 2024 07:47:42 +0800 Subject: [PATCH 05/10] [Catalyst] Fully migrate BluetoothMainSwitchPreference Bug: 372774767 Flag: com.android.settings.flags.catalyst_bluetooth_switchbar_screen Test: atest Change-Id: Ie7a78a0ef2a8739d30ece4c1c4fde5651876dc8a --- .../BluetoothMainSwitchPreference.kt | 99 ---------- .../connecteddevice/BluetoothPreference.kt | 175 ++++++++++++++++++ ...enceTest.kt => BluetoothPreferenceTest.kt} | 19 +- 3 files changed, 183 insertions(+), 110 deletions(-) delete mode 100644 src/com/android/settings/connecteddevice/BluetoothMainSwitchPreference.kt create mode 100644 src/com/android/settings/connecteddevice/BluetoothPreference.kt rename tests/robotests/src/com/android/settings/connecteddevice/{BluetoothMainSwitchPreferenceTest.kt => BluetoothPreferenceTest.kt} (75%) diff --git a/src/com/android/settings/connecteddevice/BluetoothMainSwitchPreference.kt b/src/com/android/settings/connecteddevice/BluetoothMainSwitchPreference.kt deleted file mode 100644 index bf806531135..00000000000 --- a/src/com/android/settings/connecteddevice/BluetoothMainSwitchPreference.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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.connecteddevice - -import android.bluetooth.BluetoothAdapter -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import com.android.settings.R -import com.android.settings.widget.MainSwitchBarMetadata -import com.android.settingslib.datastore.KeyValueStore -import com.android.settingslib.datastore.NoOpKeyedObservable -import com.android.settingslib.metadata.PreferenceLifecycleContext -import com.android.settingslib.metadata.PreferenceLifecycleProvider -import com.android.settingslib.metadata.ReadWritePermit - -class BluetoothMainSwitchPreference(private val bluetoothAdapter: BluetoothAdapter?) : - MainSwitchBarMetadata, PreferenceLifecycleProvider { - - private lateinit var broadcastReceiver: BroadcastReceiver - - override val key - get() = "use_bluetooth" - - override val title - get() = R.string.bluetooth_main_switch_title - - override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) = - ReadWritePermit.ALLOW - - override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = - ReadWritePermit.ALLOW - - override fun storage(context: Context) = BluetoothStateStore(bluetoothAdapter) - - override fun onStart(context: PreferenceLifecycleContext) { - broadcastReceiver = - object : BroadcastReceiver() { - override fun onReceive(receiverContext: Context, intent: Intent) { - context.notifyPreferenceChange(key) - } - } - context.registerReceiver( - broadcastReceiver, - IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED), - Context.RECEIVER_EXPORTED_UNAUDITED - ) - } - - override fun onStop(context: PreferenceLifecycleContext) { - if (::broadcastReceiver.isInitialized) { - context.unregisterReceiver(broadcastReceiver) - } - } - - override fun isEnabled(context: Context): Boolean { - return bluetoothAdapter?.state.let { - it == BluetoothAdapter.STATE_ON || it == BluetoothAdapter.STATE_OFF - } - } - - @Suppress("UNCHECKED_CAST") - class BluetoothStateStore(private val bluetoothAdapter: BluetoothAdapter?) : - NoOpKeyedObservable(), KeyValueStore { - - override fun contains(key: String) = true - - override fun getValue(key: String, valueType: Class): T? { - return (bluetoothAdapter?.state.let { - it == BluetoothAdapter.STATE_ON || it == BluetoothAdapter.STATE_TURNING_ON - }) as T - } - - override fun setValue(key: String, valueType: Class, value: T?) { - if (value is Boolean) { - if (value) { - bluetoothAdapter?.enable() - } else { - bluetoothAdapter?.disable() - } - } - } - } -} diff --git a/src/com/android/settings/connecteddevice/BluetoothPreference.kt b/src/com/android/settings/connecteddevice/BluetoothPreference.kt new file mode 100644 index 00000000000..c9b39537331 --- /dev/null +++ b/src/com/android/settings/connecteddevice/BluetoothPreference.kt @@ -0,0 +1,175 @@ +/* + * 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.connecteddevice + +import android.annotation.SuppressLint +import android.bluetooth.BluetoothAdapter +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.UserManager +import android.provider.Settings +import android.widget.Toast +import androidx.preference.Preference +import com.android.settings.PreferenceRestrictionMixin +import com.android.settings.R +import com.android.settings.network.SatelliteRepository.Companion.isSatelliteOn +import com.android.settings.network.SatelliteWarningDialogActivity +import com.android.settings.widget.MainSwitchBarMetadata +import com.android.settingslib.WirelessUtils +import com.android.settingslib.datastore.AbstractKeyedDataObservable +import com.android.settingslib.datastore.DataChangeReason +import com.android.settingslib.datastore.KeyValueStore +import com.android.settingslib.metadata.PreferenceMetadata +import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel + +@SuppressLint("MissingPermission") +class BluetoothPreference(private val bluetoothDataStore: BluetoothDataStore) : + MainSwitchBarMetadata, PreferenceRestrictionMixin, Preference.OnPreferenceChangeListener { + + override val key + get() = KEY + + override val title + get() = R.string.bluetooth_main_switch_title + + override val restrictionKeys: Array + get() = arrayOf(UserManager.DISALLOW_BLUETOOTH, UserManager.DISALLOW_CONFIG_BLUETOOTH) + + override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) = + ReadWritePermit.ALLOW + + override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = + when { + isSatelliteOn(context, 3000) || + (value == true && + !WirelessUtils.isRadioAllowed(context, Settings.Global.RADIO_BLUETOOTH)) -> + ReadWritePermit.DISALLOW + else -> ReadWritePermit.ALLOW + } + + override val sensitivityLevel + get() = SensitivityLevel.LOW_SENSITIVITY + + override fun storage(context: Context) = bluetoothDataStore + + override fun isEnabled(context: Context): Boolean { + return super.isEnabled(context) && + bluetoothDataStore.bluetoothAdapter?.state.let { + it == BluetoothAdapter.STATE_ON || it == BluetoothAdapter.STATE_OFF + } + } + + override fun bind(preference: Preference, metadata: PreferenceMetadata) { + super.bind(preference, metadata) + preference.onPreferenceChangeListener = this + } + + override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { + val context = preference.context + + if (isSatelliteOn(context, 3000)) { + context.startActivity( + Intent(context, SatelliteWarningDialogActivity::class.java) + .putExtra( + SatelliteWarningDialogActivity.EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG, + SatelliteWarningDialogActivity.TYPE_IS_BLUETOOTH, + ) + ) + return false + } + + // Show toast message if Bluetooth is not allowed in airplane mode + if ( + newValue == true && + !WirelessUtils.isRadioAllowed(context, Settings.Global.RADIO_BLUETOOTH) + ) { + Toast.makeText(context, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show() + return false + } + + return true + } + + @Suppress("UNCHECKED_CAST") + private class BluetoothStorage( + private val context: Context, + override val bluetoothAdapter: BluetoothAdapter?, + ) : AbstractKeyedDataObservable(), BluetoothDataStore { + + private var broadcastReceiver: BroadcastReceiver? = null + + override fun contains(key: String) = key == KEY && bluetoothAdapter != null + + override fun getValue(key: String, valueType: Class): T { + return (bluetoothAdapter?.state.let { + it == BluetoothAdapter.STATE_ON || it == BluetoothAdapter.STATE_TURNING_ON + }) + as T + } + + @Suppress("DEPRECATION") + override fun setValue(key: String, valueType: Class, value: T?) { + if (value is Boolean) { + if (value) { + bluetoothAdapter?.enable() + } else { + bluetoothAdapter?.disable() + } + } + } + + @SuppressLint("WrongConstant") + override fun onFirstObserverAdded() { + broadcastReceiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + notifyChange(KEY, DataChangeReason.UPDATE) + } + } + context.registerReceiver( + broadcastReceiver, + IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED), + Context.RECEIVER_EXPORTED_UNAUDITED, + ) + } + + override fun onLastObserverRemoved() { + context.unregisterReceiver(broadcastReceiver) + } + } + + companion object { + const val KEY = "use_bluetooth" + + @Suppress("DEPRECATION") + fun createDataStore(context: Context) = + createDataStore(context, BluetoothAdapter.getDefaultAdapter()) + + fun createDataStore( + context: Context, + bluetoothAdapter: BluetoothAdapter?, + ): BluetoothDataStore = BluetoothStorage(context, bluetoothAdapter) + } +} + +/** Datastore of the bluetooth preference. */ +interface BluetoothDataStore : KeyValueStore { + val bluetoothAdapter: BluetoothAdapter? +} diff --git a/tests/robotests/src/com/android/settings/connecteddevice/BluetoothMainSwitchPreferenceTest.kt b/tests/robotests/src/com/android/settings/connecteddevice/BluetoothPreferenceTest.kt similarity index 75% rename from tests/robotests/src/com/android/settings/connecteddevice/BluetoothMainSwitchPreferenceTest.kt rename to tests/robotests/src/com/android/settings/connecteddevice/BluetoothPreferenceTest.kt index 15db130795d..8b739dbc776 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/BluetoothMainSwitchPreferenceTest.kt +++ b/tests/robotests/src/com/android/settings/connecteddevice/BluetoothPreferenceTest.kt @@ -31,45 +31,42 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) -class BluetoothMainSwitchPreferenceTest { +class BluetoothPreferenceTest { @get:Rule val setFlagsRule = SetFlagsRule() private val context: Context = ApplicationProvider.getApplicationContext() private lateinit var bluetoothAdapter: BluetoothAdapter - private lateinit var bluetoothMainSwitchPreference: BluetoothMainSwitchPreference + private lateinit var bluetoothPreference: BluetoothPreference @Before fun setUp() { bluetoothAdapter = spy(BluetoothAdapter.getDefaultAdapter()) whenever(bluetoothAdapter.state).thenReturn(BluetoothAdapter.STATE_ON) - bluetoothMainSwitchPreference = BluetoothMainSwitchPreference(bluetoothAdapter) + bluetoothPreference = + BluetoothPreference(BluetoothPreference.createDataStore(context, bluetoothAdapter)) } @Test fun isEnabled_bluetoothOn_returnTrue() { - assertThat(bluetoothMainSwitchPreference.isEnabled(context)).isTrue() + assertThat(bluetoothPreference.isEnabled(context)).isTrue() } @Test fun isEnabled_bluetoothTurningOn_returnFalse() { whenever(bluetoothAdapter.state).thenReturn(BluetoothAdapter.STATE_TURNING_ON) - assertThat(bluetoothMainSwitchPreference.isEnabled(context)).isFalse() + assertThat(bluetoothPreference.isEnabled(context)).isFalse() } @Test fun storageSetOff_turnOff() { - bluetoothMainSwitchPreference - .storage(context) - .setBoolean(bluetoothMainSwitchPreference.key, false) + bluetoothPreference.storage(context).setBoolean(bluetoothPreference.key, false) verify(bluetoothAdapter).disable() } @Test fun storageSetOn_turnOn() { - bluetoothMainSwitchPreference - .storage(context) - .setBoolean(bluetoothMainSwitchPreference.key, true) + bluetoothPreference.storage(context).setBoolean(bluetoothPreference.key, true) verify(bluetoothAdapter).enable() } From 1c63279c3d403f2504a21f4046e004456d0ed087 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Mon, 9 Dec 2024 17:22:04 +0800 Subject: [PATCH 06/10] [Catalyst] Migrate BluetoothFooterPreference Bug: 372774767 Flag: com.android.settings.flags.catalyst_bluetooth_switchbar_screen Test: devtool Change-Id: Ic1f5cc897bcbaf8775fb4b841625c9a40644adfc --- .../BluetoothFooterPreference.kt | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/com/android/settings/connecteddevice/BluetoothFooterPreference.kt diff --git a/src/com/android/settings/connecteddevice/BluetoothFooterPreference.kt b/src/com/android/settings/connecteddevice/BluetoothFooterPreference.kt new file mode 100644 index 00000000000..72e51a36a3a --- /dev/null +++ b/src/com/android/settings/connecteddevice/BluetoothFooterPreference.kt @@ -0,0 +1,96 @@ +/* + * 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.connecteddevice + +import android.app.settings.SettingsEnums +import android.content.Context +import android.content.Intent +import android.util.Log +import androidx.preference.Preference +import com.android.settings.R +import com.android.settings.bluetooth.Utils +import com.android.settings.core.SubSettingLauncher +import com.android.settings.location.BluetoothScanningFragment +import com.android.settingslib.metadata.PreferenceMetadata +import com.android.settingslib.metadata.PreferenceSummaryProvider +import com.android.settingslib.preference.PreferenceBinding +import com.android.settingslib.widget.FooterPreference + +class BluetoothFooterPreference(private val bluetoothDataStore: BluetoothDataStore) : + PreferenceMetadata, PreferenceBinding, PreferenceSummaryProvider { + + override val key: String + get() = KEY + + override fun isIndexable(context: Context) = false + + override fun dependencies(context: Context) = arrayOf(BluetoothPreference.KEY) + + override fun intent(context: Context): Intent? = subSettingLauncher(context).toIntent() + + override fun createWidget(context: Context) = FooterPreference(context) + + override fun bind(preference: Preference, metadata: PreferenceMetadata) { + super.bind(preference, metadata) + preference.isSelectable = false + val bluetoothDisabled = bluetoothDataStore.getBoolean(BluetoothPreference.KEY) != true + val footerPreference = preference as FooterPreference + val context = preference.context + if (bluetoothDisabled && Utils.isBluetoothScanningEnabled(context)) { + footerPreference.setLearnMoreText(context.getString(R.string.bluetooth_scan_change)) + footerPreference.setLearnMoreAction { subSettingLauncher(context).launch() } + } else { + footerPreference.setLearnMoreText("") + footerPreference.setLearnMoreAction(null) + } + } + + private fun subSettingLauncher(context: Context) = + SubSettingLauncher(context) + .setDestination(BluetoothScanningFragment::class.java.name) + .setSourceMetricsCategory(SettingsEnums.BLUETOOTH_FRAGMENT) + + override fun getSummary(context: Context): CharSequence? { + val bluetoothDisabled = bluetoothDataStore.getBoolean(BluetoothPreference.KEY) != true + val resId = + if (bluetoothDisabled && Utils.isBluetoothScanningEnabled(context)) { + when (isAutoOnFeatureAvailable()) { + true -> R.string.bluetooth_scanning_on_info_message_auto_on_available + else -> R.string.bluetooth_scanning_on_info_message + } + } else { + when (isAutoOnFeatureAvailable()) { + true -> R.string.bluetooth_empty_list_bluetooth_off_auto_on_available + else -> R.string.bluetooth_empty_list_bluetooth_off + } + } + return context.getString(resId) + } + + private fun isAutoOnFeatureAvailable() = + try { + bluetoothDataStore.bluetoothAdapter?.isAutoOnSupported == true + } catch (e: Exception) { + Log.e(TAG, "isAutoOnSupported failed", e) + false + } + + companion object { + const val KEY = "bluetooth_screen_footer" + const val TAG = "BluetoothFooterPreference" + } +} From efd08c4bfbde14345efd0416e09d2e3704e0cac7 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Mon, 9 Dec 2024 17:40:48 +0800 Subject: [PATCH 07/10] [Catalyst] Migrate bluetooth screen to catalyst Bug: 372774767 Flag: com.android.settings.flags.catalyst_bluetooth_switchbar_screen Test: devtool Change-Id: I08730e63f17b5fe57268e57f796f88f52ef5caa0 --- res/xml/bluetooth_screen.xml | 1 + .../BluetoothDashboardFragment.java | 44 +++++++++++++++++-- .../BluetoothDashboardScreen.kt | 13 +++++- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/res/xml/bluetooth_screen.xml b/res/xml/bluetooth_screen.xml index e815d44410b..51cff333e38 100644 --- a/res/xml/bluetooth_screen.xml +++ b/res/xml/bluetooth_screen.xml @@ -16,6 +16,7 @@ Date: Tue, 10 Dec 2024 15:19:58 +0800 Subject: [PATCH 08/10] [Catalyst] Fix MainSwitchBarPreference NPE when configuration changed When activity is recreated (due to configuration is changed), preference screen is created before SettingsActivity sets view layout and results in NPE. As a workaround, save the states inside MainSwitchBarPreference to fix the problem. Bug: 332201912 Flag: EXEMPT library Test: manual Change-Id: I052cd0a521dac203a5f58963c550911e7a741e6f --- .../widget/MainSwitchBarPreference.kt | 75 ++++++++++++++++++- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/widget/MainSwitchBarPreference.kt b/src/com/android/settings/widget/MainSwitchBarPreference.kt index 5cf96397184..b3b341c75a9 100644 --- a/src/com/android/settings/widget/MainSwitchBarPreference.kt +++ b/src/com/android/settings/widget/MainSwitchBarPreference.kt @@ -17,6 +17,8 @@ package com.android.settings.widget import android.content.Context +import android.os.Parcel +import android.os.Parcelable import android.widget.CompoundButton import android.widget.CompoundButton.OnCheckedChangeListener import androidx.preference.TwoStatePreference @@ -28,24 +30,30 @@ import com.android.settingslib.widget.MainSwitchBar class MainSwitchBarPreference(context: Context, private val metadata: MainSwitchBarMetadata) : TwoStatePreference(context), OnCheckedChangeListener, MainSwitchBar.PreChangeListener { - private val mainSwitchBar: MainSwitchBar = (context as SettingsActivity).switchBar + // main switch bar might be null when configuration is just changed + private val mainSwitchBar: MainSwitchBar? + get() = (context as SettingsActivity).switchBar override fun setTitle(title: CharSequence?) { - mainSwitchBar.setTitle(title) + mainSwitchBar?.setTitle(title) + super.setTitle(title) } override fun setSummary(summary: CharSequence?) { - mainSwitchBar.setSummary(summary) + mainSwitchBar?.setSummary(summary) + super.setSummary(summary) } override fun setEnabled(enabled: Boolean) { - mainSwitchBar.isEnabled = enabled + mainSwitchBar?.isEnabled = enabled + super.setEnabled(enabled) } // Preference.setVisible is final, we cannot override it fun updateVisibility() { // always make preference invisible, the UI visibility is reflected on MainSwitchBar isVisible = false + val mainSwitchBar = mainSwitchBar ?: return if ((metadata as? PreferenceAvailabilityProvider)?.isAvailable(context) != false) { mainSwitchBar.show() } else { @@ -54,6 +62,7 @@ class MainSwitchBarPreference(context: Context, private val metadata: MainSwitch } override fun setChecked(checked: Boolean) { + val mainSwitchBar = mainSwitchBar ?: return // remove listener to update UI only mainSwitchBar.removeOnSwitchChangeListener(this) mainSwitchBar.isChecked = checked @@ -62,6 +71,7 @@ class MainSwitchBarPreference(context: Context, private val metadata: MainSwitch override fun onAttached() { super.onAttached() + val mainSwitchBar = mainSwitchBar!! mainSwitchBar.setPreChangeListener(this) mainSwitchBar.addOnSwitchChangeListener(this) } @@ -76,8 +86,65 @@ class MainSwitchBarPreference(context: Context, private val metadata: MainSwitch } override fun onDetached() { + val mainSwitchBar = mainSwitchBar!! mainSwitchBar.removeOnSwitchChangeListener(this) mainSwitchBar.setPreChangeListener(null) super.onDetached() } + + override fun onSaveInstanceState(): Parcelable = + SavedState(super.onSaveInstanceState()!!).also { + it.isEnabled = isEnabled + it.title = title + it.summary = summary + it.mainSwitchBarState = mainSwitchBar?.onSaveInstanceState() + } + + override fun onRestoreInstanceState(state: Parcelable?) { + val savedState = state as SavedState + super.onRestoreInstanceState(savedState.superState) + isEnabled = savedState.isEnabled + title = savedState.title + summary = savedState.summary + mainSwitchBar?.onRestoreInstanceState(savedState.mainSwitchBarState!!) + } + + private class SavedState : BaseSavedState { + var isEnabled: Boolean = false + var title: CharSequence? = null + var summary: CharSequence? = null + var mainSwitchBarState: Parcelable? = null + + constructor(source: Parcel) : super(source) { + isEnabled = source.readBoolean() + title = source.readCharSequence() + summary = source.readCharSequence() + val stateClass = MainSwitchBar.SavedState::class.java + mainSwitchBarState = source.readParcelable(stateClass.classLoader, stateClass) + } + + constructor(superState: Parcelable) : super(superState) + + override fun writeToParcel(dest: Parcel, flags: Int) { + super.writeToParcel(dest, flags) + dest.writeBoolean(isEnabled) + dest.writeCharSequence(title) + dest.writeCharSequence(summary) + dest.writeParcelable(mainSwitchBarState, flags) + } + + companion object { + @JvmField + val CREATOR: Parcelable.Creator = + object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): SavedState { + return SavedState(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } + } } From 3d595b169d61b1d0fac08165b822aeedd0d4e3ee Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Tue, 10 Dec 2024 17:27:39 +0800 Subject: [PATCH 09/10] [Catalyst] Fix enable state for Wi-Fi hotspot NO_IFTTT=Catalyst only Bug: 383232049 Flag: com.android.settings.flags.catalyst_tether_settings Test: manual Change-Id: I067e97820a19822eaad2e93e30d42f7f767bf855 --- .../settings/wifi/tether/WifiHotspotSwitchPreference.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/wifi/tether/WifiHotspotSwitchPreference.kt b/src/com/android/settings/wifi/tether/WifiHotspotSwitchPreference.kt index c185e02c949..931583a6db6 100644 --- a/src/com/android/settings/wifi/tether/WifiHotspotSwitchPreference.kt +++ b/src/com/android/settings/wifi/tether/WifiHotspotSwitchPreference.kt @@ -100,7 +100,7 @@ class WifiHotspotSwitchPreference(context: Context, dataSaverStore: KeyValueStor .toIntent() override fun isEnabled(context: Context) = - wifiHotspotStore.dataSaverStore.getBoolean(DATA_SAVER_KEY) == true && + wifiHotspotStore.dataSaverStore.getBoolean(DATA_SAVER_KEY) != true && super.isEnabled(context) override val restrictionKeys @@ -193,6 +193,7 @@ class WifiHotspotSwitchPreference(context: Context, dataSaverStore: KeyValueStor super.bind(preference, metadata) (preference as PrimarySwitchPreference).apply { isChecked = preferenceDataStore!!.getBoolean(key, false) + isSwitchEnabled = isEnabled } } From 6ba4109c2b02c020d175dcf7701db504d49625ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Tue, 10 Dec 2024 12:47:31 +0100 Subject: [PATCH 10/10] Use different "None" strings for Messages and Calls in Modes breakthrough Bug: 330870672 Test: manual Flag: android.app.modes_ui Change-Id: I33c6ebce9281313d5d1cb483b82dfd90d1ff48a1 --- .../modes/ZenModePrioritySendersPreferenceController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java index 4dfb242c239..254bbfdd9a8 100644 --- a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java @@ -148,7 +148,8 @@ class ZenModePrioritySendersPreferenceController makeSelectorPreference(KEY_ANY, R.string.zen_mode_from_anyone, null, mIsMessages, true); makeSelectorPreference(KEY_NONE, - R.string.zen_mode_none_messages, null, mIsMessages, true); + mIsMessages ? R.string.zen_mode_none_messages : R.string.zen_mode_none_calls, + null, mIsMessages, true); } super.displayPreference(screen); }