From c311354d1069429f008bce17e4459230fefa6526 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Fri, 2 Aug 2024 11:43:20 +0800 Subject: [PATCH 01/11] Show uncheck toggle when wep not supported When wep not supported, wep is always not allowed, so we should show unchecked toggle for "Allow WEP networks" Bug: 356326814 Flag: EXEMPT bug fix Test: manual - on Network preferences Test: atest WepNetworksPreferenceControllerTest Change-Id: I408434fbbf48e1010710ecf3bf9f7988e521b076 --- .../wifi/WepNetworksPreferenceController.kt | 94 +++++++------- .../WepNetworksPreferenceControllerTest.kt | 115 +++++++++++------- 2 files changed, 123 insertions(+), 86 deletions(-) diff --git a/src/com/android/settings/wifi/WepNetworksPreferenceController.kt b/src/com/android/settings/wifi/WepNetworksPreferenceController.kt index 2ce4bcd4d4f..bad7201128c 100644 --- a/src/com/android/settings/wifi/WepNetworksPreferenceController.kt +++ b/src/com/android/settings/wifi/WepNetworksPreferenceController.kt @@ -42,6 +42,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn /** Controller that controls whether the WEP network can be connected. */ class WepNetworksPreferenceController(context: Context, preferenceKey: String) : @@ -49,68 +51,74 @@ class WepNetworksPreferenceController(context: Context, preferenceKey: String) : var wifiManager = context.getSystemService(WifiManager::class.java)!! - override fun getAvailabilityStatus() = if (Flags.androidVWifiApi()) AVAILABLE - else UNSUPPORTED_ON_DEVICE + override fun getAvailabilityStatus() = + if (Flags.androidVWifiApi()) AVAILABLE else UNSUPPORTED_ON_DEVICE @Composable override fun Content() { - val checked by wepAllowedFlow.flow.collectAsStateWithLifecycle(initialValue = null) + val isWepSupported: Boolean? = + isWepSupportedFlow.collectAsStateWithLifecycle(initialValue = null).value + val isWepAllowed: Boolean? = + wepAllowedFlow.flow.collectAsStateWithLifecycle(initialValue = null).value var openDialog by rememberSaveable { mutableStateOf(false) } - val wifiInfo = wifiManager.connectionInfo - SwitchPreference(object : SwitchPreferenceModel { - override val title = stringResource(R.string.wifi_allow_wep_networks) - override val summary = { getSummary() } - override val checked = { checked } - override val changeable: () -> Boolean - get() = { carrierAllowed } - override val onCheckedChange: (Boolean) -> Unit = { newChecked -> - if (!newChecked && wifiInfo.currentSecurityType == WifiEntry.SECURITY_WEP) { - openDialog = true - } else { - wifiManager.setWepAllowed(newChecked) - wepAllowedFlow.override(newChecked) + SwitchPreference( + object : SwitchPreferenceModel { + override val title = stringResource(R.string.wifi_allow_wep_networks) + override val summary = { getSummary(isWepSupported) } + override val checked = { + if (isWepSupported == true) isWepAllowed else isWepSupported } - } - }) + override val changeable: () -> Boolean + get() = { isWepSupported == true } + + override val onCheckedChange: (Boolean) -> Unit = { newChecked -> + val wifiInfo = wifiManager.connectionInfo + if (!newChecked && wifiInfo.currentSecurityType == WifiEntry.SECURITY_WEP) { + openDialog = true + } else { + wifiManager.setWepAllowed(newChecked) + wepAllowedFlow.override(newChecked) + } + } + }) if (openDialog) { SettingsAlertDialogWithIcon( onDismissRequest = { openDialog = false }, - confirmButton = AlertDialogButton( - stringResource(R.string.sim_action_yes) - ) { - wifiManager.setWepAllowed(false) - wepAllowedFlow.override(false) - openDialog = false - }, + confirmButton = + AlertDialogButton(stringResource(R.string.sim_action_yes)) { + wifiManager.setWepAllowed(false) + wepAllowedFlow.override(false) + openDialog = false + }, dismissButton = - AlertDialogButton( - stringResource(R.string.wifi_cancel) - ) { openDialog = false }, + AlertDialogButton(stringResource(R.string.wifi_cancel)) { openDialog = false }, title = stringResource(R.string.wifi_settings_wep_networks_disconnect_title), text = { Text( stringResource(R.string.wifi_settings_wep_networks_disconnect_summary), modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center + textAlign = TextAlign.Center, ) }) } } - override fun getSummary(): String = mContext.getString( - if (carrierAllowed) { - R.string.wifi_allow_wep_networks_summary - } else { - R.string.wifi_allow_wep_networks_summary_carrier_not_allow - } - ) + private fun getSummary(isWepSupported: Boolean?): String = + mContext.getString( + when (isWepSupported) { + true -> R.string.wifi_allow_wep_networks_summary + false -> R.string.wifi_allow_wep_networks_summary_carrier_not_allow + null -> R.string.summary_placeholder + }) - private val carrierAllowed: Boolean - get() = wifiManager.isWepSupported + private val isWepSupportedFlow = + flow { emit(wifiManager.isWepSupported) }.flowOn(Dispatchers.Default) - val wepAllowedFlow = OverridableFlow(callbackFlow { - wifiManager.queryWepAllowed(Dispatchers.Default.asExecutor(), ::trySend) + val wepAllowedFlow = + OverridableFlow( + callbackFlow { + wifiManager.queryWepAllowed(Dispatchers.Default.asExecutor(), ::trySend) - awaitClose { } - }) -} \ No newline at end of file + awaitClose {} + }) +} diff --git a/tests/spa_unit/src/com/android/settings/wifi/WepNetworksPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/wifi/WepNetworksPreferenceControllerTest.kt index 49e6a171efd..9183096fd7b 100644 --- a/tests/spa_unit/src/com/android/settings/wifi/WepNetworksPreferenceControllerTest.kt +++ b/tests/spa_unit/src/com/android/settings/wifi/WepNetworksPreferenceControllerTest.kt @@ -17,6 +17,7 @@ package com.android.settings.wifi import android.content.Context +import android.net.wifi.WifiInfo import android.net.wifi.WifiManager import androidx.compose.ui.test.assertIsOff import androidx.compose.ui.test.assertIsOn @@ -30,7 +31,6 @@ import androidx.preference.PreferenceManager import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.R -import com.android.settings.dashboard.DashboardFragment import com.android.settings.spa.preference.ComposePreference import com.android.settingslib.spa.testutils.onDialogText import com.android.wifitrackerlib.WifiEntry @@ -45,29 +45,31 @@ import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.spy +import org.mockito.kotlin.stub @RunWith(AndroidJUnit4::class) class WepNetworksPreferenceControllerTest { - @get:Rule - val composeTestRule = createComposeRule() + @get:Rule val composeTestRule = createComposeRule() private var wepAllowed = true - private var mockWifiInfo = mock { - on { it.currentSecurityType } doReturn WifiEntry.SECURITY_EAP - on { it.ssid } doReturn SSID - } - - private var mockWifiManager = mock { - on { queryWepAllowed(any(), any()) } doAnswer { - @Suppress("UNCHECKED_CAST") - val consumer = it.arguments[1] as Consumer - consumer.accept(wepAllowed) + private var mockWifiInfo = + mock { + on { currentSecurityType } doReturn WifiEntry.SECURITY_EAP + on { ssid } doReturn SSID + } + + private var mockWifiManager = + mock { + on { queryWepAllowed(any(), any()) } doAnswer + { + @Suppress("UNCHECKED_CAST") val consumer = it.arguments[1] as Consumer + consumer.accept(wepAllowed) + } + on { isWepSupported } doReturn true + on { connectionInfo } doReturn mockWifiInfo } - on { it.isWepSupported } doReturn true - on { it.connectionInfo } doReturn mockWifiInfo - } private var context: Context = spy(ApplicationProvider.getApplicationContext()) { @@ -85,74 +87,101 @@ class WepNetworksPreferenceControllerTest { } @Test - fun wepAllowedTrue_turnOn() { + fun isChecked_wepSupportedAndAllowed_isOn() { + mockWifiManager.stub { on { isWepSupported } doReturn true } wepAllowed = true - composeTestRule.setContent { - controller.Content() - } - composeTestRule.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks)) + composeTestRule.setContent { controller.Content() } + + composeTestRule + .onNodeWithText(context.getString(R.string.wifi_allow_wep_networks)) .assertIsOn() } @Test - fun wepAllowedFalse_turnOff() { + fun isChecked_wepSupportedAndNotAllowed_isOff() { + mockWifiManager.stub { on { isWepSupported } doReturn true } wepAllowed = false - composeTestRule.setContent { - controller.Content() - } - composeTestRule.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks)) + composeTestRule.setContent { controller.Content() } + + composeTestRule + .onNodeWithText(context.getString(R.string.wifi_allow_wep_networks)) + .assertIsOff() + } + + @Test + fun isChecked_wepNotSupportedAndAllowed_isOff() { + mockWifiManager.stub { on { isWepSupported } doReturn false } + wepAllowed = true + + composeTestRule.setContent { controller.Content() } + + composeTestRule + .onNodeWithText(context.getString(R.string.wifi_allow_wep_networks)) + .assertIsOff() + } + + @Test + fun isChecked_wepNotSupportedAndNotAllowed_isOff() { + mockWifiManager.stub { on { isWepSupported } doReturn false } + wepAllowed = false + + composeTestRule.setContent { controller.Content() } + + composeTestRule + .onNodeWithText(context.getString(R.string.wifi_allow_wep_networks)) .assertIsOff() } @Test fun onClick_turnOn() { wepAllowed = false - composeTestRule.setContent { - controller.Content() - } + composeTestRule.setContent { controller.Content() } composeTestRule.onRoot().performClick() - composeTestRule.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks)) + composeTestRule + .onNodeWithText(context.getString(R.string.wifi_allow_wep_networks)) .assertIsOn() } @Test fun onClick_turnOff() { wepAllowed = true - composeTestRule.setContent { - controller.Content() - } + composeTestRule.setContent { controller.Content() } composeTestRule.onRoot().performClick() - composeTestRule.onNodeWithText(context.getString(R.string.wifi_allow_wep_networks)) + composeTestRule + .onNodeWithText(context.getString(R.string.wifi_allow_wep_networks)) .assertIsOff() } @Test fun whenClick_wepAllowed_openDialog() { wepAllowed = true - Mockito.`when`(mockWifiInfo.currentSecurityType).thenReturn(WifiEntry.SECURITY_WEP) - composeTestRule.setContent { - controller.Content() + mockWifiInfo.stub { + on { currentSecurityType } doReturn WifiEntry.SECURITY_WEP } + composeTestRule.setContent { controller.Content() } composeTestRule.onRoot().performClick() - composeTestRule.onDialogText(context.getString(R.string.wifi_disconnect_button_text)) + composeTestRule + .onDialogText(context.getString(R.string.wifi_disconnect_button_text)) .isDisplayed() } @Test fun whenClick_wepDisallowed_openDialog() { wepAllowed = false - Mockito.`when`(mockWifiInfo.currentSecurityType).thenReturn(WifiEntry.SECURITY_WEP) - composeTestRule.setContent { - controller.Content() + mockWifiInfo.stub { + on { currentSecurityType } doReturn WifiEntry.SECURITY_WEP } + composeTestRule.setContent { controller.Content() } + composeTestRule.onRoot().performClick() - composeTestRule.onDialogText(context.getString(R.string.wifi_disconnect_button_text)) + composeTestRule + .onDialogText(context.getString(R.string.wifi_disconnect_button_text)) .isNotDisplayed() } @@ -160,4 +189,4 @@ class WepNetworksPreferenceControllerTest { const val TEST_KEY = "test_key" const val SSID = "ssid" } -} \ No newline at end of file +} From 7edad1c84ad7981ead7d3325aeacad9c86a419c5 Mon Sep 17 00:00:00 2001 From: Yiyi Shen Date: Wed, 31 Jul 2024 20:28:09 +0800 Subject: [PATCH 02/11] [Audiosharing] Returns BluetoothDevice when fetchConnectedDevicesByGroupId CachedBluetoothDevice can change its member variable mDevice, so we can not rely on the CachedBluetoothDevice#getDevice when we add source to it. This change will return BluetoothDevice instead of CachedBluetoothDevice when fetchConnectedDevicesByGroupId, so that we won't add source to unintended BluetoothDevice via CachedBluetoothDevice#getDevice. Fix: 350877510 Test: atest Flag: com.android.settingslib.flags.enable_le_audio_sharing Change-Id: I4ee41b8f1449e7176f0a05a7dd4e59034c161824 --- ...oSharingCallAudioPreferenceController.java | 15 +-- .../AudioSharingDialogHandler.java | 50 ++++----- .../AudioSharingSwitchBarController.java | 39 +++---- .../audiosharing/AudioSharingUtils.java | 100 +++++++++++------- .../AudioStreamConfirmDialogTest.java | 1 - 5 files changed, 107 insertions(+), 98 deletions(-) diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java index 33c1a6c6cb6..168fce51dc4 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java @@ -66,7 +66,7 @@ import java.util.concurrent.atomic.AtomicBoolean; /** PreferenceController to control the dialog to choose the active device for calls and alarms */ public class AudioSharingCallAudioPreferenceController extends AudioSharingBasePreferenceController implements BluetoothCallback { - private static final String TAG = "CallsAndAlarmsPreferenceController"; + private static final String TAG = "CallAudioPrefController"; private static final String PREF_KEY = "calls_and_alarms"; @VisibleForTesting @@ -85,7 +85,7 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP private final ContentObserver mSettingsObserver; private final MetricsFeatureProvider mMetricsFeatureProvider; @Nullable private Fragment mFragment; - Map> mGroupedConnectedDevices = new HashMap<>(); + Map> mGroupedConnectedDevices = new HashMap<>(); private List mDeviceItemsInSharingSession = new ArrayList<>(); private final AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false); @@ -210,17 +210,18 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP "Skip set fallback active device: unchanged"); return; } - List devices = + List devices = mGroupedConnectedDevices.getOrDefault( item.getGroupId(), ImmutableList.of()); CachedBluetoothDevice lead = - AudioSharingUtils.getLeadDevice(devices); + AudioSharingUtils.getLeadDevice( + mCacheManager, devices); if (lead != null) { Log.d( TAG, "Set fallback active device: " + lead.getDevice() - .getAnonymizedAddress()); + .getAnonymizedAddress()); lead.setActive(); logCallAudioDeviceChange(currentGroupId, lead); } else { @@ -347,8 +348,8 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP */ private void updateSummary() { updateDeviceItemsInSharingSession(); - int fallbackActiveGroupId = BluetoothUtils.getPrimaryGroupIdForBroadcast( - mContext.getContentResolver()); + int fallbackActiveGroupId = + BluetoothUtils.getPrimaryGroupIdForBroadcast(mContext.getContentResolver()); if (fallbackActiveGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { for (AudioSharingDeviceItem item : mDeviceItemsInSharingSession) { if (item.getGroupId() == fallbackActiveGroupId) { diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java index 472cb440063..4ee405d0f35 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java @@ -16,6 +16,8 @@ package com.android.settings.connecteddevice.audiosharing; +import static java.util.stream.Collectors.toList; + import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; @@ -38,6 +40,7 @@ import com.android.settings.dashboard.DashboardFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -49,14 +52,14 @@ import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.Executor; public class AudioSharingDialogHandler { - private static final String TAG = "AudioSharingDialogHandler"; + private static final String TAG = "AudioSharingDlgHandler"; private final Context mContext; private final Fragment mHostFragment; @Nullable private final LocalBluetoothManager mLocalBtManager; + @Nullable private final CachedBluetoothDeviceManager mDeviceManager; @Nullable private final LocalBluetoothLeBroadcast mBroadcast; @Nullable private final LocalBluetoothLeBroadcastAssistant mAssistant; private final MetricsFeatureProvider mMetricsFeatureProvider; @@ -163,6 +166,7 @@ public class AudioSharingDialogHandler { mContext = context; mHostFragment = fragment; mLocalBtManager = Utils.getLocalBluetoothManager(context); + mDeviceManager = mLocalBtManager != null ? mLocalBtManager.getCachedDeviceManager() : null; mBroadcast = mLocalBtManager != null ? mLocalBtManager.getProfileManager().getLeAudioBroadcastProfile() @@ -212,7 +216,7 @@ public class AudioSharingDialogHandler { if (isBroadcasting) { // Show stop audio sharing dialog when an ineligible (non LE audio) remote device // connected during a sharing session. - Map> groupedDevices = + Map> groupedDevices = AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager); List deviceItemsInSharingSession = AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem( @@ -256,19 +260,19 @@ public class AudioSharingDialogHandler { @NonNull CachedBluetoothDevice cachedDevice, boolean isBroadcasting, boolean userTriggered) { - Map> groupedDevices = + Map> groupedDevices = AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager); BluetoothDevice btDevice = cachedDevice.getDevice(); String deviceAddress = btDevice == null ? "" : btDevice.getAnonymizedAddress(); + int groupId = BluetoothUtils.getGroupId(cachedDevice); if (isBroadcasting) { // If another device within the same is already in the sharing session, add source to // the device automatically. - int groupId = BluetoothUtils.getGroupId(cachedDevice); if (groupedDevices.containsKey(groupId) && groupedDevices.get(groupId).stream() .anyMatch( device -> - BluetoothUtils.hasConnectedBroadcastSource( + BluetoothUtils.hasConnectedBroadcastSourceForBtDevice( device, mLocalBtManager))) { Log.d( TAG, @@ -352,14 +356,17 @@ public class AudioSharingDialogHandler { } else { // Build a list of AudioSharingDeviceItem for connected devices other than cachedDevice. List deviceItems = new ArrayList<>(); - for (List devices : groupedDevices.values()) { + for (Map.Entry> entry : groupedDevices.entrySet()) { + if (entry.getKey() == groupId) continue; // Use random device in the group within the sharing session to represent the group. - CachedBluetoothDevice device = devices.get(0); - if (BluetoothUtils.getGroupId(device) - == BluetoothUtils.getGroupId(cachedDevice)) { - continue; + for (BluetoothDevice device : entry.getValue()) { + CachedBluetoothDevice cDevice = + mDeviceManager != null ? mDeviceManager.findDevice(device) : null; + if (cDevice != null) { + deviceItems.add(AudioSharingUtils.buildAudioSharingDeviceItem(cDevice)); + break; + } } - deviceItems.add(AudioSharingUtils.buildAudioSharingDeviceItem(device)); } // Show audio sharing join dialog when the second eligible (LE audio) remote // device connect and no sharing session. @@ -368,13 +375,10 @@ public class AudioSharingDialogHandler { new AudioSharingJoinDialogFragment.DialogEventListener() { @Override public void onShareClick() { - mTargetSinks = new ArrayList<>(); - for (List devices : - groupedDevices.values()) { - for (CachedBluetoothDevice device : devices) { - mTargetSinks.add(device.getDevice()); - } - } + mTargetSinks = + groupedDevices.values().stream() + .flatMap(items -> items.stream()) + .collect(toList()); Log.d(TAG, "Start broadcast with sinks = " + mTargetSinks.size()); if (mBroadcast != null) { mBroadcast.startPrivateBroadcast(); @@ -493,7 +497,7 @@ public class AudioSharingDialogHandler { } private void removeSourceForGroup( - int groupId, Map> groupedDevices) { + int groupId, Map> groupedDevices) { if (mAssistant == null) { Log.d(TAG, "Fail to add source due to null profiles, group = " + groupId); return; @@ -503,8 +507,6 @@ public class AudioSharingDialogHandler { return; } groupedDevices.getOrDefault(groupId, ImmutableList.of()).stream() - .map(CachedBluetoothDevice::getDevice) - .filter(Objects::nonNull) .forEach( device -> { for (BluetoothLeBroadcastReceiveState source : @@ -515,7 +517,7 @@ public class AudioSharingDialogHandler { } private void addSourceForGroup( - int groupId, Map> groupedDevices) { + int groupId, Map> groupedDevices) { if (mBroadcast == null || mAssistant == null) { Log.d(TAG, "Fail to add source due to null profiles, group = " + groupId); return; @@ -525,8 +527,6 @@ public class AudioSharingDialogHandler { return; } groupedDevices.getOrDefault(groupId, ImmutableList.of()).stream() - .map(CachedBluetoothDevice::getDevice) - .filter(Objects::nonNull) .forEach( device -> mAssistant.addSource( diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java index 3c0faba5db8..8396e48afd1 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java @@ -48,7 +48,6 @@ import com.android.settings.core.BasePreferenceController; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.SettingsMainSwitchBar; import com.android.settingslib.bluetooth.BluetoothUtils; -import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -63,17 +62,15 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; public class AudioSharingSwitchBarController extends BasePreferenceController implements DefaultLifecycleObserver, - OnCheckedChangeListener, - LocalBluetoothProfileManager.ServiceListener { - private static final String TAG = "AudioSharingSwitchBarCtl"; + OnCheckedChangeListener, + LocalBluetoothProfileManager.ServiceListener { + private static final String TAG = "AudioSharingSwitchCtlr"; private static final String PREF_KEY = "audio_sharing_main_switch"; interface OnAudioSharingStateChangedListener { @@ -103,7 +100,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController private final Executor mExecutor; private final MetricsFeatureProvider mMetricsFeatureProvider; private final OnAudioSharingStateChangedListener mListener; - private Map> mGroupedConnectedDevices = new HashMap<>(); + private Map> mGroupedConnectedDevices = new HashMap<>(); private List mTargetActiveSinks = new ArrayList<>(); private List mDeviceItemsForSharing = new ArrayList<>(); @VisibleForTesting IntentFilter mIntentFilter; @@ -341,8 +338,8 @@ public class AudioSharingSwitchBarController extends BasePreferenceController // FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST is always true in // prod. We can turn off the flag for debug purpose. if (FeatureFlagUtils.isEnabled( - mContext, - FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST) + mContext, + FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST) && mAssistant.getAllConnectedDevices().isEmpty()) { // Pop up dialog to ask users to connect at least one lea buds before audio sharing. AudioSharingUtils.postOnMainThread( @@ -454,13 +451,11 @@ public class AudioSharingSwitchBarController extends BasePreferenceController mDeviceItemsForSharing = new ArrayList<>(deviceItems); mTargetActiveSinks = new ArrayList<>(); if (!deviceItems.isEmpty() && deviceItems.get(0).isActive()) { - for (CachedBluetoothDevice device : + // If active device exists for audio sharing, share to it + // automatically once the broadcast is started. + mTargetActiveSinks = mGroupedConnectedDevices.getOrDefault( - deviceItems.get(0).getGroupId(), ImmutableList.of())) { - // If active device exists for audio sharing, share to it - // automatically once the broadcast is started. - mTargetActiveSinks.add(device.getDevice()); - } + deviceItems.get(0).getGroupId(), ImmutableList.of()); mDeviceItemsForSharing.remove(0); } if (mBroadcast != null) { @@ -488,7 +483,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController boolean isStateReady = isBluetoothOn() && AudioSharingUtils.isAudioSharingProfileReady( - mProfileManager); + mProfileManager); AudioSharingUtils.postOnMainThread( mContext, () -> { @@ -541,12 +536,8 @@ public class AudioSharingSwitchBarController extends BasePreferenceController @Override public void onItemClick(@NonNull AudioSharingDeviceItem item) { AudioSharingUtils.addSourceToTargetSinks( - mGroupedConnectedDevices - .getOrDefault(item.getGroupId(), ImmutableList.of()) - .stream() - .map(CachedBluetoothDevice::getDevice) - .filter(Objects::nonNull) - .collect(Collectors.toList()), + mGroupedConnectedDevices.getOrDefault( + item.getGroupId(), ImmutableList.of()), mBtManager); mGroupedConnectedDevices.clear(); mDeviceItemsForSharing.clear(); @@ -575,8 +566,8 @@ public class AudioSharingSwitchBarController extends BasePreferenceController @NonNull ViewGroup host, @NonNull View view, @NonNull AccessibilityEvent event) { if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED && (event.getContentChangeTypes() - & AccessibilityEvent.CONTENT_CHANGE_TYPE_ENABLED) - != 0) { + & AccessibilityEvent.CONTENT_CHANGE_TYPE_ENABLED) + != 0) { Log.d(TAG, "Skip accessibility event for CONTENT_CHANGE_TYPE_ENABLED"); return false; } diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java index 50f9c9a8cc9..0c2dc367171 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java @@ -22,6 +22,8 @@ import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtil import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.MetricKey.METRIC_KEY_SOURCE_PAGE_ID; import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.MetricKey.METRIC_KEY_USER_TRIGGERED; +import static java.util.stream.Collectors.toList; + import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastMetadata; @@ -44,10 +46,11 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.bluetooth.VolumeControlProfile; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; +import java.util.Objects; public class AudioSharingUtils { private static final String TAG = "AudioSharingUtils"; @@ -62,15 +65,15 @@ public class AudioSharingUtils { } /** - * Fetch {@link CachedBluetoothDevice}s connected to the broadcast assistant. The devices are - * grouped by CSIP group id. + * Fetch {@link BluetoothDevice}s connected to the broadcast assistant. The devices are grouped + * by CSIP group id. * * @param localBtManager The BT manager to provide BT functions. * @return A map of connected devices grouped by CSIP group id. */ - public static Map> fetchConnectedDevicesByGroupId( + public static Map> fetchConnectedDevicesByGroupId( @Nullable LocalBluetoothManager localBtManager) { - Map> groupedDevices = new HashMap<>(); + Map> groupedDevices = new HashMap<>(); if (localBtManager == null) { Log.d(TAG, "Skip fetchConnectedDevicesByGroupId due to bt manager is null"); return groupedDevices; @@ -99,7 +102,7 @@ public class AudioSharingUtils { if (!groupedDevices.containsKey(groupId)) { groupedDevices.put(groupId, new ArrayList<>()); } - groupedDevices.get(groupId).add(cachedDevice); + groupedDevices.get(groupId).add(device); } if (DEBUG) { Log.d(TAG, "fetchConnectedDevicesByGroupId: " + groupedDevices); @@ -122,11 +125,16 @@ public class AudioSharingUtils { */ public static List buildOrderedConnectedLeadDevices( @Nullable LocalBluetoothManager localBtManager, - Map> groupedConnectedDevices, + Map> groupedConnectedDevices, boolean filterByInSharing) { List orderedDevices = new ArrayList<>(); - for (List devices : groupedConnectedDevices.values()) { - CachedBluetoothDevice leadDevice = getLeadDevice(devices); + if (localBtManager == null) { + Log.d(TAG, "Skip buildOrderedConnectedLeadDevices due to bt manager is null"); + return orderedDevices; + } + CachedBluetoothDeviceManager deviceManager = localBtManager.getCachedDeviceManager(); + for (List devices : groupedConnectedDevices.values()) { + CachedBluetoothDevice leadDevice = getLeadDevice(deviceManager, devices); if (leadDevice == null) { Log.d(TAG, "Skip due to no lead device"); continue; @@ -141,52 +149,39 @@ public class AudioSharingUtils { } orderedDevices.add(leadDevice); } - orderedDevices.sort( - (CachedBluetoothDevice d1, CachedBluetoothDevice d2) -> { - // Active above not inactive - int comparison = - (isActiveLeAudioDevice(d2) ? 1 : 0) - - (isActiveLeAudioDevice(d1) ? 1 : 0); - if (comparison != 0) return comparison; - // Bonded above not bonded - comparison = - (d2.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) - - (d1.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0); - if (comparison != 0) return comparison; - // Bond timestamp available above unavailable - comparison = - (d2.getBondTimestamp() != null ? 1 : 0) - - (d1.getBondTimestamp() != null ? 1 : 0); - if (comparison != 0) return comparison; - // Order by bond timestamp if it is available - // Otherwise order by device name - return d1.getBondTimestamp() != null - ? d1.getBondTimestamp().compareTo(d2.getBondTimestamp()) - : d1.getName().compareTo(d2.getName()); - }); + orderedDevices.sort(sCachedDeviceComparator); return orderedDevices; } /** * Get the lead device from a list of devices with same group id. * + * @param deviceManager CachedBluetoothDeviceManager * @param devices A list of devices with same group id. * @return The lead device */ @Nullable public static CachedBluetoothDevice getLeadDevice( - @NonNull List devices) { - if (devices.isEmpty()) return null; - for (CachedBluetoothDevice device : devices) { - if (!device.getMemberDevice().isEmpty()) { - return device; + @Nullable CachedBluetoothDeviceManager deviceManager, + @NonNull List devices) { + if (deviceManager == null || devices.isEmpty()) return null; + List cachedDevices = + devices.stream() + .map(device -> deviceManager.findDevice(device)) + .filter(Objects::nonNull) + .collect(toList()); + for (CachedBluetoothDevice cachedDevice : cachedDevices) { + if (!cachedDevice.getMemberDevice().isEmpty()) { + return cachedDevice; } } - CachedBluetoothDevice leadDevice = devices.get(0); + CachedBluetoothDevice leadDevice = cachedDevices.isEmpty() ? null : cachedDevices.get(0); Log.d( TAG, "No lead device in the group, pick arbitrary device as the lead: " - + leadDevice.getDevice().getAnonymizedAddress()); + + (leadDevice == null + ? "null" + : leadDevice.getDevice().getAnonymizedAddress())); return leadDevice; } @@ -206,13 +201,13 @@ public class AudioSharingUtils { @NonNull public static List buildOrderedConnectedLeadAudioSharingDeviceItem( @Nullable LocalBluetoothManager localBtManager, - Map> groupedConnectedDevices, + Map> groupedConnectedDevices, boolean filterByInSharing) { return buildOrderedConnectedLeadDevices( localBtManager, groupedConnectedDevices, filterByInSharing) .stream() .map(AudioSharingUtils::buildAudioSharingDeviceItem) - .collect(Collectors.toList()); + .collect(toList()); } /** Build {@link AudioSharingDeviceItem} from {@link CachedBluetoothDevice}. */ @@ -361,4 +356,27 @@ public class AudioSharingUtils { Pair.create(METRIC_KEY_CANDIDATE_DEVICE_COUNT.ordinal(), candidateDeviceCount) }; } + + private static final Comparator sCachedDeviceComparator = + (CachedBluetoothDevice d1, CachedBluetoothDevice d2) -> { + // Active above not inactive + int comparison = + (isActiveLeAudioDevice(d2) ? 1 : 0) - (isActiveLeAudioDevice(d1) ? 1 : 0); + if (comparison != 0) return comparison; + // Bonded above not bonded + comparison = + (d2.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) + - (d1.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0); + if (comparison != 0) return comparison; + // Bond timestamp available above unavailable + comparison = + (d2.getBondTimestamp() != null ? 1 : 0) + - (d1.getBondTimestamp() != null ? 1 : 0); + if (comparison != 0) return comparison; + // Order by bond timestamp if it is available + // Otherwise order by device name + return d1.getBondTimestamp() != null + ? d1.getBondTimestamp().compareTo(d2.getBondTimestamp()) + : d1.getName().compareTo(d2.getName()); + }; } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialogTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialogTest.java index e00a146244d..9c83fa6f8ee 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialogTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialogTest.java @@ -23,7 +23,6 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Aud import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; From af87233e1e713dd5b91789210a8a78ffc880c24a Mon Sep 17 00:00:00 2001 From: josephpv Date: Wed, 24 Jul 2024 14:20:11 +0000 Subject: [PATCH 03/11] Show different error screen for PS creation based on error This contains the change to show a different fragment on private space creation error based on the error code returned by UserManager. On private space create error checks for the returned error code and if PS is not supported on the device showns error screen containing a link to Help Center atricle to find out more about the reason for profile creation failure. ACTION_PRIVATE_SPACE_SETUP_SPACE_ERRORS metric is logged with the error code on create error else 0 on sucessful space creation. Recording: b/340130375#comment17 Bug: 340130375 Test: Manual Flag: android.multiuser.show_different_creation_error_for_unsupported_devices Change-Id: Ifa0345fb6aad64599009f8aa79d168f57fd35c03 --- .../privatespace_main_context_nav.xml | 6 ++ res/values/strings.xml | 6 ++ ...PrivateProfileCreationRestrictedError.java | 99 +++++++++++++++++++ .../PrivateSpaceCreationFragment.java | 31 +++++- .../privatespace/PrivateSpaceMaintainer.java | 9 ++ 5 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/com/android/settings/privatespace/PrivateProfileCreationRestrictedError.java diff --git a/res/navigation/privatespace_main_context_nav.xml b/res/navigation/privatespace_main_context_nav.xml index 3eb57d3ecda..e20eaff5206 100644 --- a/res/navigation/privatespace_main_context_nav.xml +++ b/res/navigation/privatespace_main_context_nav.xml @@ -35,6 +35,9 @@ + + Couldn\u2019t set up a private space Try Again + + Exit + + Private space isn\u2019t available.\nView possible causes + + View possible causes Choose a new lock for private space? diff --git a/src/com/android/settings/privatespace/PrivateProfileCreationRestrictedError.java b/src/com/android/settings/privatespace/PrivateProfileCreationRestrictedError.java new file mode 100644 index 00000000000..d2bdb8cbe79 --- /dev/null +++ b/src/com/android/settings/privatespace/PrivateProfileCreationRestrictedError.java @@ -0,0 +1,99 @@ +/* + * 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.privatespace; + +import android.app.Activity; +import android.app.settings.SettingsEnums; +import android.os.Bundle; +import android.text.util.Linkify; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.settings.R; +import com.android.settings.core.InstrumentedFragment; + +import com.google.android.setupcompat.template.FooterBarMixin; +import com.google.android.setupcompat.template.FooterButton; +import com.google.android.setupdesign.GlifLayout; + +import java.util.regex.Pattern; + +public class PrivateProfileCreationRestrictedError extends InstrumentedFragment { + private static final String TAG = "PrivateSpaceCreationErr"; + + @NonNull + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + GlifLayout rootView = + (GlifLayout) + inflater.inflate(R.layout.privatespace_creation_error, container, false); + final FooterBarMixin mixin = rootView.getMixin(FooterBarMixin.class); + mixin.setPrimaryButton( + new FooterButton.Builder(getContext()) + .setText(R.string.private_space_exit_label) + .setListener(onExit()) + .setButtonType(FooterButton.ButtonType.NEXT) + .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary) + .build()); + OnBackPressedCallback callback = + new OnBackPressedCallback(true /* enabled by default */) { + @Override + public void handleOnBackPressed() { + // Handle the back button event. We intentionally don't want to allow back + // button to work in this screen during the setup flow. + } + }; + requireActivity().getOnBackPressedDispatcher().addCallback(this, callback); + rootView.setDescriptionText(R.string.private_space_error_description); + TextView textView = rootView.getDescriptionTextView(); + Pattern pattern = Pattern.compile(getString(R.string.private_space_error_causes_text)); + Linkify.addLinks( + textView, + pattern, + getString(R.string.private_space_learn_more_url)); + + return rootView; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.PRIVATE_SPACE_SETUP_SPACE_CREATION_ERROR; + } + + private View.OnClickListener onExit() { + return v -> { + Activity activity = getActivity(); + if (activity != null) { + mMetricsFeatureProvider.action( + getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_CANCEL_CREATE_SPACE); + Log.i(TAG, "private space setup exited"); + activity.finish(); + } + }; + } +} + diff --git a/src/com/android/settings/privatespace/PrivateSpaceCreationFragment.java b/src/com/android/settings/privatespace/PrivateSpaceCreationFragment.java index ce85d7238cc..0bfedbd394e 100644 --- a/src/com/android/settings/privatespace/PrivateSpaceCreationFragment.java +++ b/src/com/android/settings/privatespace/PrivateSpaceCreationFragment.java @@ -29,6 +29,7 @@ import android.net.NetworkInfo; import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.os.UserManager; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -49,6 +50,7 @@ public class PrivateSpaceCreationFragment extends InstrumentedFragment { private static final String TAG = "PrivateSpaceCreateFrag"; private static final int PRIVATE_SPACE_CREATE_POST_DELAY_MS = 1000; private static final int PRIVATE_SPACE_ACCOUNT_LOGIN_POST_DELAY_MS = 5000; + private static final int PRIVATE_SPACE_SETUP_NO_ERROR = 0; private static final Handler sHandler = new Handler(Looper.getMainLooper()); private Runnable mRunnable = () -> { @@ -122,6 +124,11 @@ public class PrivateSpaceCreationFragment extends InstrumentedFragment { Log.i(TAG, "Private Space created"); mMetricsFeatureProvider.action( getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SPACE_CREATED, true); + if (android.multiuser.Flags.showDifferentCreationErrorForUnsupportedDevices()) { + mMetricsFeatureProvider.action( + getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SPACE_ERRORS, + PRIVATE_SPACE_SETUP_NO_ERROR); + } if (isConnectedToInternet()) { registerReceiver(); sHandler.postDelayed( @@ -132,8 +139,18 @@ public class PrivateSpaceCreationFragment extends InstrumentedFragment { } } else { mMetricsFeatureProvider.action( - getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SPACE_CREATED, false); - showPrivateSpaceErrorScreen(); + getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SPACE_CREATED, + false); + if (android.multiuser.Flags.showDifferentCreationErrorForUnsupportedDevices()) { + int errorCode = PrivateSpaceMaintainer.getInstance( + getActivity()).getPrivateSpaceCreateError(); + mMetricsFeatureProvider.action( + getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SPACE_ERRORS, + errorCode); + showPrivateSpaceErrorScreen(errorCode); + } else { + showPrivateSpaceErrorScreen(); + } } } @@ -147,6 +164,16 @@ public class PrivateSpaceCreationFragment extends InstrumentedFragment { .navigate(R.id.action_create_profile_error); } + private void showPrivateSpaceErrorScreen(int errorCode) { + if (errorCode == UserManager.USER_OPERATION_ERROR_USER_RESTRICTED + || errorCode == UserManager.USER_OPERATION_ERROR_PRIVATE_PROFILE) { + NavHostFragment.findNavController(PrivateSpaceCreationFragment.this) + .navigate(R.id.action_create_profile_error_restrict); + } else { + showPrivateSpaceErrorScreen(); + } + } + /** Returns true if device has an active internet connection, false otherwise. */ private boolean isConnectedToInternet() { ConnectivityManager cm = diff --git a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java index ec044daacbf..dd6a4bb6ab4 100644 --- a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java +++ b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java @@ -60,6 +60,7 @@ public class PrivateSpaceMaintainer { private final Context mContext; private final UserManager mUserManager; private final ActivityManager mActivityManager; + private int mErrorCode; @GuardedBy("this") private UserHandle mUserHandle; private final KeyguardManager mKeyguardManager; @@ -111,6 +112,9 @@ public class PrivateSpaceMaintainer { userName, USER_TYPE_PROFILE_PRIVATE, new ArraySet<>()); } catch (Exception e) { Log.e(TAG, "Error creating private space", e); + if (android.multiuser.Flags.showDifferentCreationErrorForUnsupportedDevices()) { + mErrorCode = ((UserManager.UserOperationException) e).getUserOperationResult(); + } return false; } @@ -312,6 +316,11 @@ public class PrivateSpaceMaintainer { return mUserManager.canAddPrivateProfile() || doesPrivateSpaceExist(); } + /** Returns the error code for private space creation failure*/ + public int getPrivateSpaceCreateError() { + return mErrorCode; + } + /** Returns true if private space exists and is running, otherwise returns false */ @VisibleForTesting synchronized boolean isPrivateProfileRunning() { From bf0b93b1041580b29a9859f1b498d88ecb23c36d Mon Sep 17 00:00:00 2001 From: Allen Su Date: Sun, 4 Aug 2024 16:52:12 +0000 Subject: [PATCH 04/11] [ToA] Fix unit test failure Flag: TEST_ONLY Bug: 328697623 Test: atest TermsOfAddressCategoryControllerTest Change-Id: Iddbf5b1ca16e455dfa12b9a9a6543d366b62d55e --- .../TermsOfAddressCategoryControllerTest.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/unit/src/com/android/settings/localepicker/TermsOfAddressCategoryControllerTest.java b/tests/unit/src/com/android/settings/localepicker/TermsOfAddressCategoryControllerTest.java index e316b2556c6..b025abdf06a 100644 --- a/tests/unit/src/com/android/settings/localepicker/TermsOfAddressCategoryControllerTest.java +++ b/tests/unit/src/com/android/settings/localepicker/TermsOfAddressCategoryControllerTest.java @@ -17,13 +17,11 @@ package com.android.settings.localepicker; import static com.android.settings.core.BasePreferenceController.AVAILABLE; -import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; -import android.app.settings.SettingsEnums; import android.content.Context; import android.os.Looper; @@ -66,14 +64,6 @@ public class TermsOfAddressCategoryControllerTest { Locale.setDefault(mCacheLocale); } - @Test - public void getAvailabilityStatus_returnUnavailable() { - Locale.setDefault(Locale.forLanguageTag("fr-CA")); - - assertThat(mTermsOfAddressCategoryController.getAvailabilityStatus()).isEqualTo( - CONDITIONALLY_UNAVAILABLE); - } - @Test public void getAvailabilityStatus_returnAvailable() { Locale.setDefault(Locale.forLanguageTag("fr-FR")); From 210529efc434a8c396456997504ed2e43f3bdb23 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Mon, 5 Aug 2024 10:23:02 +0800 Subject: [PATCH 05/11] Reduce flaky of BackgroundInstalledAppsPageProviderTest Bug: 355413226 Flag: EXEMPT test only Test: atest BackgroundInstalledAppsPageProviderTest Change-Id: Iede656b7cf34bac82718f0aff12ee28cbaa9d8f8 --- ...BackgroundInstalledAppsPageProviderTest.kt | 40 +++++++------------ 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/tests/spa_unit/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProviderTest.kt index 6a6b91b2922..5ced84b0b5b 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProviderTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProviderTest.kt @@ -37,45 +37,32 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.junit.MockitoJUnit -import org.mockito.junit.MockitoRule import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class BackgroundInstalledAppsPageProviderTest { - @get:Rule - val composeTestRule = createComposeRule() + @get:Rule val composeTestRule = createComposeRule() - @get:Rule - val mockito: MockitoRule = MockitoJUnit.rule() + private val mockPackageManager = mock() - private val context: Context = ApplicationProvider.getApplicationContext() + private val context: Context = + spy(ApplicationProvider.getApplicationContext()) { + on { packageManager } doReturn mockPackageManager + } - @Mock - private lateinit var mockContext: Context - - @Mock - private lateinit var mockPackageManager: PackageManager - - @Mock - private lateinit var mockBackgroundInstallControlService: IBackgroundInstallControlService - - private var packageInfoFlagsCaptor = argumentCaptor() + private val mockBackgroundInstallControlService = mock() private val fakeNavControllerWrapper = FakeNavControllerWrapper() - @Before - fun setup() { - whenever(mockContext.packageManager).thenReturn(mockPackageManager) - } @Test fun allAppListPageProvider_name() { assertThat(BackgroundInstalledAppsPageProvider.name) @@ -108,7 +95,7 @@ class BackgroundInstalledAppsPageProviderTest { setInjectEntry(false) - composeTestRule.onNodeWithText("0 apps").assertIsDisplayed() + composeTestRule.waitUntilExists(hasText("0 apps")) } @Test @@ -200,7 +187,8 @@ class BackgroundInstalledAppsPageProviderTest { @Test fun backgroundInstalledAppsWithGroupingListModel_transform() = runTest { - val listModel = BackgroundInstalledAppsWithGroupingListModel(mockContext) + val packageInfoFlagsCaptor = argumentCaptor() + val listModel = BackgroundInstalledAppsWithGroupingListModel(context) whenever(mockPackageManager.getPackageInfoAsUser( eq(TEST_PACKAGE_NAME), packageInfoFlagsCaptor.capture(), @@ -218,7 +206,7 @@ class BackgroundInstalledAppsPageProviderTest { @Test fun backgroundInstalledAppsWithGroupingListModel_filter() = runTest { - val listModel = BackgroundInstalledAppsWithGroupingListModel(mockContext) + val listModel = BackgroundInstalledAppsWithGroupingListModel(context) listModel.setBackgroundInstallControlService(mockBackgroundInstallControlService) whenever(mockBackgroundInstallControlService.getBackgroundInstalledPackages( PackageManager.MATCH_ALL.toLong(), From b9db3c5395f4831ed14a748ae0648d63c46303ba Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Mon, 5 Aug 2024 12:58:51 +0800 Subject: [PATCH 06/11] Settings search for Roaming Fix: 351740003 Flag: EXEMPT bug fix Test: manual - search roaming Change-Id: I260449bdea7dcaae6a2d44c6810cf2638c18d9a4 --- res/xml/mobile_network_settings.xml | 3 ++- .../MobileNetworkSettingsSearchIndex.kt | 2 ++ .../telephony/RoamingPreferenceController.kt | 24 ++++++++++++------- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/res/xml/mobile_network_settings.xml b/res/xml/mobile_network_settings.xml index 6c0f7b5450c..63d8a3d7b50 100644 --- a/res/xml/mobile_network_settings.xml +++ b/res/xml/mobile_network_settings.xml @@ -85,9 +85,10 @@ android:summary="@string/auto_data_switch_summary" settings:controller="com.android.settings.network.telephony.AutoDataSwitchPreferenceController"/> + = listOf( + RoamingSearchItem(context), MmsMessageSearchItem(context), NrAdvancedCallingSearchItem(context), ) diff --git a/src/com/android/settings/network/telephony/RoamingPreferenceController.kt b/src/com/android/settings/network/telephony/RoamingPreferenceController.kt index 2529d41324e..7633677dcb4 100644 --- a/src/com/android/settings/network/telephony/RoamingPreferenceController.kt +++ b/src/com/android/settings/network/telephony/RoamingPreferenceController.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.res.stringResource import androidx.fragment.app.FragmentManager import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.settings.R +import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchItem import com.android.settings.spa.preference.ComposePreferenceController import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel import com.android.settingslib.spaprivileged.model.enterprise.Restrictions @@ -47,6 +48,7 @@ constructor( private var telephonyManager = context.getSystemService(TelephonyManager::class.java)!! private val carrierConfigRepository = CarrierConfigRepository(context) + private val roamingSearchItem = RoamingSearchItem(context) fun init(fragmentManager: FragmentManager, subId: Int) { this.fragmentManager = fragmentManager @@ -54,14 +56,8 @@ constructor( telephonyManager = telephonyManager.createForSubscriptionId(subId) } - override fun getAvailabilityStatus(): Int { - if (!SubscriptionManager.isValidSubscriptionId(subId)) return CONDITIONALLY_UNAVAILABLE - val isForceHomeNetwork = - carrierConfigRepository.getBoolean( - subId, CarrierConfigManager.KEY_FORCE_HOME_NETWORK_BOOL) - - return if (isForceHomeNetwork) CONDITIONALLY_UNAVAILABLE else AVAILABLE - } + override fun getAvailabilityStatus() = + if (roamingSearchItem.isAvailable(subId)) AVAILABLE else CONDITIONALLY_UNAVAILABLE @Composable override fun Content() { @@ -101,5 +97,17 @@ constructor( companion object { private const val DIALOG_TAG = "MobileDataDialog" + + class RoamingSearchItem(context: Context) : MobileNetworkSettingsSearchItem { + override val key = "button_roaming_key" + override val title: String = context.getString(R.string.roaming) + + private val carrierConfigRepository = CarrierConfigRepository(context) + + override fun isAvailable(subId: Int): Boolean = + SubscriptionManager.isValidSubscriptionId(subId) && + !carrierConfigRepository.getBoolean( + subId, CarrierConfigManager.KEY_FORCE_HOME_NETWORK_BOOL) + } } } From 8e7b62c8872ea3b7c804b755e678e371f4603a9c Mon Sep 17 00:00:00 2001 From: Haijie Hong Date: Mon, 5 Aug 2024 13:37:40 +0800 Subject: [PATCH 07/11] Remove duplicate "lifecycle.addObserver(this)" It's already been added in superclass BluetoothDetailsController. BUG: 343317785 FLAG: EXEMPT simple fix Test: atest BluetoothDetailsProfilesControllerTest Test: atest BluetoothDetailsPairOtherControllerTest Change-Id: I7a9255cea5bb18b29562fa72898356c7a01cd31c --- .../settings/bluetooth/BluetoothDetailsPairOtherController.java | 1 - .../settings/bluetooth/BluetoothDetailsProfilesController.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherController.java b/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherController.java index d1d00d8a646..e29dcb0ee40 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherController.java @@ -50,7 +50,6 @@ public class BluetoothDetailsPairOtherController extends BluetoothDetailsControl CachedBluetoothDevice device, Lifecycle lifecycle) { super(context, fragment, device, lifecycle); - lifecycle.addObserver(this); } @Override diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java index 015d427f8cd..c0d221ace5c 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java @@ -106,7 +106,6 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll mProfileManager = mManager.getProfileManager(); mCachedDevice = device; mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mManager, mCachedDevice); - lifecycle.addObserver(this); } @Override From b377b1c0e4a5465ef7d09e37eb1c0f51525b8cb6 Mon Sep 17 00:00:00 2001 From: chelseahao Date: Mon, 5 Aug 2024 14:20:17 +0800 Subject: [PATCH 08/11] [Audiosharing] Increase click target. Bug: 357012346 Flag: com.android.settingslib.flags.enable_le_audio_qr_code_private_broadcast_sharing Test: atest Change-Id: I2ecaf4d3c8c2b249da4b25b6658f1aef3c43209e --- res/layout/audio_sharing_password_dialog.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/layout/audio_sharing_password_dialog.xml b/res/layout/audio_sharing_password_dialog.xml index f1a78bcaad4..2bdf505290d 100644 --- a/res/layout/audio_sharing_password_dialog.xml +++ b/res/layout/audio_sharing_password_dialog.xml @@ -53,8 +53,8 @@ From 58bc2e3dcacfba62cfca7d2c25e6076673d94fdb Mon Sep 17 00:00:00 2001 From: Jason Chang Date: Mon, 5 Aug 2024 08:55:10 +0000 Subject: [PATCH 09/11] Fix the option to set a different screen lock in the "Set a PIN" screen is displayed off the screen when the device language is set to Arabic To call optButton's MarginLayoutParams setMarginStart() instead of set leftMargin. Flag: EXEMPT bugfix Bug: 355422248 Test: build Forrest ROM and verify the UI Change-Id: I700b83937ecd8509864fa3c80826de0ba0437ca2 --- src/com/android/settings/password/PasswordUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/password/PasswordUtils.java b/src/com/android/settings/password/PasswordUtils.java index a54df94fa90..8c8afc2e9ef 100644 --- a/src/com/android/settings/password/PasswordUtils.java +++ b/src/com/android/settings/password/PasswordUtils.java @@ -116,7 +116,7 @@ public final class PasswordUtils extends com.android.settingslib.Utils { final ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - lp.leftMargin = layoutTitleParams.leftMargin; + lp.setMarginStart(layoutTitleParams.leftMargin); lp.topMargin = (int) context.getResources().getDimensionPixelSize( R.dimen.screen_lock_options_button_margin_top); optButton.setPadding(0, 0, 0, 0); From 773afdfaf7629351fba968be08117f08c3ea27dd Mon Sep 17 00:00:00 2001 From: "Priyanka Advani (xWF)" Date: Fri, 2 Aug 2024 18:35:03 +0000 Subject: [PATCH 10/11] Revert "Expand ToA language support" This reverts commit a60a38dd39ae7809d8c7b5e77bf3e88921337c32. Reason for revert: Droidmonitor created revert due to b/357174479. Change-Id: I2db3afbb148bf7a9d66d395c56e490d837cb8b93 --- res/values/arrays.xml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/res/values/arrays.xml b/res/values/arrays.xml index cc681073f2c..4680936b99b 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -1503,14 +1503,12 @@ + fr-CA fr - es - it - de From 6b6ab6391880c6d47d896d9bcf3618509f1086f1 Mon Sep 17 00:00:00 2001 From: Allen Su Date: Mon, 5 Aug 2024 15:08:29 +0000 Subject: [PATCH 11/11] Revert^2 "Expand ToA language support" This reverts commit 773afdfaf7629351fba968be08117f08c3ea27dd. Reason for revert: b/357055415 has been fixed. Revert is unnecessary Flag: TEST_ONLY Bug: 328697623 Test: atest TermsOfAddressCategoryControllerTest Change-Id: Ic1477e3dfdade68b81bbdcdb0c98109064278eb4 --- res/values/arrays.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 4680936b99b..cc681073f2c 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -1503,12 +1503,14 @@ - fr-CA fr + es + it + de