From 5b4748919a97e8a0fd05889a801f0c464b2cc054 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Fri, 30 Aug 2024 11:16:34 +0800 Subject: [PATCH 1/7] Use simSpecificCarrierId for new apn Currently in TelephonyProvider, it uses tm.getSimSpecificCarrierId() to get the specific carrier id to retrieve the apns, update Settings to match. Fix: 360917123 Flag: EXEMPT bug fix Test: manual - new apn Test: atest ApnRepositoryTest Change-Id: I982e0026acdf34ccec7508c41f83718894f7da57 --- .../settings/network/apn/ApnRepository.kt | 9 +++-- .../settings/network/apn/ApnRepositoryTest.kt | 34 +++++++------------ 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/src/com/android/settings/network/apn/ApnRepository.kt b/src/com/android/settings/network/apn/ApnRepository.kt index 843371501b7..7ed0c865127 100644 --- a/src/com/android/settings/network/apn/ApnRepository.kt +++ b/src/com/android/settings/network/apn/ApnRepository.kt @@ -21,10 +21,10 @@ import android.content.Context import android.database.Cursor import android.net.Uri import android.provider.Telephony -import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.util.Log import com.android.settings.R +import com.android.settings.network.telephony.telephonyManager import com.android.settingslib.utils.ThreadUtils import java.util.Locale @@ -178,12 +178,11 @@ fun isItemExist(apnData: ApnData, context: Context): String? { } fun Context.getApnIdMap(subId: Int): Map { - val subInfo = getSystemService(SubscriptionManager::class.java)!! - .getActiveSubscriptionInfo(subId) - val carrierId = subInfo.carrierId + val telephonyManager = telephonyManager(subId) + val carrierId = telephonyManager.simSpecificCarrierId return if (carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) { mapOf(Telephony.Carriers.CARRIER_ID to carrierId) } else { - mapOf(Telephony.Carriers.NUMERIC to subInfo.mccString + subInfo.mncString) + mapOf(Telephony.Carriers.NUMERIC to telephonyManager.simOperator) }.also { Log.d(TAG, "[$subId] New APN item with id: $it") } } diff --git a/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt index 415531882a2..d2f16d75bbb 100644 --- a/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt @@ -21,8 +21,6 @@ import android.content.Context import android.database.MatrixCursor import android.net.Uri import android.provider.Telephony -import android.telephony.SubscriptionInfo -import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -40,19 +38,15 @@ class ApnRepositoryTest { private val contentResolver = mock() - private val mockSubscriptionInfo = mock { - on { mccString } doReturn MCC - on { mncString } doReturn MNC - } + private val mockTelephonyManager = + mock { on { createForSubscriptionId(SUB_ID) } doReturn mock } - private val mockSubscriptionManager = mock { - on { getActiveSubscriptionInfo(SUB_ID) } doReturn mockSubscriptionInfo - } + private val context: Context = + spy(ApplicationProvider.getApplicationContext()) { + on { contentResolver } doReturn contentResolver + on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager + } - private val context: Context = spy(ApplicationProvider.getApplicationContext()) { - on { contentResolver } doReturn contentResolver - on { getSystemService(SubscriptionManager::class.java) } doReturn mockSubscriptionManager - } private val uri = mock {} @Test @@ -91,9 +85,7 @@ class ApnRepositoryTest { @Test fun getApnIdMap_knownCarrierId() { - mockSubscriptionInfo.stub { - on { carrierId } doReturn CARRIER_ID - } + mockTelephonyManager.stub { on { simSpecificCarrierId } doReturn CARRIER_ID } val idMap = context.getApnIdMap(SUB_ID) @@ -102,19 +94,19 @@ class ApnRepositoryTest { @Test fun getApnIdMap_unknownCarrierId() { - mockSubscriptionInfo.stub { - on { carrierId } doReturn TelephonyManager.UNKNOWN_CARRIER_ID + mockTelephonyManager.stub { + on { simSpecificCarrierId } doReturn TelephonyManager.UNKNOWN_CARRIER_ID + on { simOperator } doReturn SIM_OPERATOR } val idMap = context.getApnIdMap(SUB_ID) - assertThat(idMap).containsExactly(Telephony.Carriers.NUMERIC, MCC + MNC) + assertThat(idMap).containsExactly(Telephony.Carriers.NUMERIC, SIM_OPERATOR) } private companion object { const val SUB_ID = 2 const val CARRIER_ID = 10 - const val MCC = "310" - const val MNC = "101" + const val SIM_OPERATOR = "310101" } } From bece2128d2076021373284d33bdc9d90158eb7c0 Mon Sep 17 00:00:00 2001 From: Yiyi Shen Date: Thu, 29 Aug 2024 19:07:47 +0800 Subject: [PATCH 2/7] [Audiosharing] Avoid dialog when onPlaybackStarted after call ends Test: atest Flag: com.android.settingslib.flags.enable_le_audio_sharing Bug: 362714470 Bug: 355222285 Change-Id: I9f6e1138f0877b607fb75ffef2ff249ef1114e6d --- .../AudioSharingSwitchBarController.java | 30 ++++++++++++++----- .../AudioSharingSwitchBarControllerTest.java | 29 ++++++++++++++++++ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java index 8e091885213..c0d67ccbb7a 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java @@ -201,6 +201,19 @@ public class AudioSharingSwitchBarController extends BasePreferenceController + reason + ", broadcastId = " + broadcastId); + if (mAssistant == null + || mAssistant.getAllConnectedDevices().stream() + .anyMatch( + device -> BluetoothUtils + .hasActiveLocalBroadcastSourceForBtDevice( + device, mBtManager))) { + Log.d( + TAG, + "Skip handleOnBroadcastReady: null assistant or " + + "sink has active local source."); + cleanUp(); + return; + } handleOnBroadcastReady(); } @@ -554,8 +567,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController mGroupedConnectedDevices.getOrDefault( mDeviceItemsForSharing.get(0).getGroupId(), ImmutableList.of()), mBtManager); - mGroupedConnectedDevices.clear(); - mDeviceItemsForSharing.clear(); + cleanUp(); // TODO: Add metric for auto add by intent return; } @@ -565,8 +577,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController StartIntentHandleStage.HANDLED.ordinal()); if (mFragment == null) { Log.d(TAG, "handleOnBroadcastReady: dialog fail to show due to null fragment."); - mGroupedConnectedDevices.clear(); - mDeviceItemsForSharing.clear(); + cleanUp(); return; } showDialog(eventData); @@ -581,14 +592,12 @@ public class AudioSharingSwitchBarController extends BasePreferenceController mGroupedConnectedDevices.getOrDefault( item.getGroupId(), ImmutableList.of()), mBtManager); - mGroupedConnectedDevices.clear(); - mDeviceItemsForSharing.clear(); + cleanUp(); } @Override public void onCancelClick() { - mGroupedConnectedDevices.clear(); - mDeviceItemsForSharing.clear(); + cleanUp(); } }; AudioSharingUtils.postOnMainThread( @@ -657,6 +666,11 @@ public class AudioSharingSwitchBarController extends BasePreferenceController }); } + private void cleanUp() { + mGroupedConnectedDevices.clear(); + mDeviceItemsForSharing.clear(); + } + private enum StartIntentHandleStage { TO_HANDLE, HANDLE_AUTO_ADD, diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java index 558bc10bace..5073119d197 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java @@ -443,6 +443,7 @@ public class AudioSharingSwitchBarControllerTest { mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true); when(mBtnView.isEnabled()).thenReturn(true); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1)); + when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of()); when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); doNothing().when(mBroadcast).startPrivateBroadcast(); mController = @@ -468,12 +469,38 @@ public class AudioSharingSwitchBarControllerTest { assertThat(childFragments).isEmpty(); } + @Test + public void onPlaybackStarted_hasLocalSource_noDialog() { + FeatureFlagUtils.setEnabled( + mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true); + when(mBtnView.isEnabled()).thenReturn(true); + when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1)); + BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class); + when(state.getBroadcastId()).thenReturn(1); + when(mBroadcast.getLatestBroadcastId()).thenReturn(1); + when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(state)); + when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); + doNothing().when(mBroadcast).startPrivateBroadcast(); + mController.onCheckedChanged(mBtnView, /* isChecked= */ true); + verify(mBroadcast).startPrivateBroadcast(); + mController.mBroadcastCallback.onPlaybackStarted(0, 0); + shadowOf(Looper.getMainLooper()).idle(); + + verify(mAssistant, never()).addSource(any(), any(), anyBoolean()); + verify(mFeatureFactory.metricsFeatureProvider, never()) + .action(any(Context.class), eq(SettingsEnums.ACTION_AUTO_JOIN_AUDIO_SHARING)); + + List childFragments = mParentFragment.getChildFragmentManager().getFragments(); + assertThat(childFragments).isEmpty(); + } + @Test public void onPlaybackStarted_showJoinAudioSharingDialog() { FeatureFlagUtils.setEnabled( mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true); when(mBtnView.isEnabled()).thenReturn(true); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1)); + when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of()); doNothing().when(mBroadcast).startPrivateBroadcast(); mController.onCheckedChanged(mBtnView, /* isChecked= */ true); when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); @@ -519,6 +546,7 @@ public class AudioSharingSwitchBarControllerTest { mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true); when(mBtnView.isEnabled()).thenReturn(true); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1)); + when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of()); when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); doNothing().when(mBroadcast).startPrivateBroadcast(); mController.onCheckedChanged(mBtnView, /* isChecked= */ true); @@ -545,6 +573,7 @@ public class AudioSharingSwitchBarControllerTest { mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true); when(mBtnView.isEnabled()).thenReturn(true); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2, mDevice1)); + when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of()); when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata); doNothing().when(mBroadcast).startPrivateBroadcast(); mController.onCheckedChanged(mBtnView, /* isChecked= */ true); From d54d112e17c6ed63638d22d69ed6bfdb4775f61d Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Fri, 30 Aug 2024 15:23:17 +0800 Subject: [PATCH 3/7] Fix WifiCallingPreferenceController crash When sub id is invalid. Bug: 325956182 Flag: EXEMPT bug fix Test: adb shell am start -a android.settings.NETWORK_OPERATOR_SETTINGS Change-Id: I09995095e2f38030eca0c9b5898d910530ee6237 --- .../network/telephony/WifiCallingPreferenceController.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt index e04763a88c1..9b68970d750 100644 --- a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt +++ b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.kt @@ -81,6 +81,12 @@ open class WifiCallingPreferenceController @JvmOverloads constructor( } override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { + // Sub id could invalid, if this page is opened from external action and no sim is + // active. + // Ignore this case, since this page will be finished soon. + return + } wifiCallingRepositoryFactory(subId).wifiCallingReadyFlow() .collectLatestWithLifecycle(viewLifecycleOwner) { isReady -> preference.isVisible = isReady From 1b3a315d06318440e70b78ad58eeb30fdd667faa Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Fri, 30 Aug 2024 15:47:14 +0800 Subject: [PATCH 4/7] New isSubscriptionVisibleFlow Use this instead of mSubscriptionInfoEntity, so MobileNetworkSettings not closed unexpectedly. Bug: 358238959 Flag: EXEMPT bug fix Test: force stop settings & settings search - mobile settings Test: adb shell am start -a android.settings.NETWORK_OPERATOR_SETTINGS Change-Id: I34286da808600d8b9faa0da85a59665707ecfde3 --- .../telephony/MobileNetworkSettings.java | 18 +++++++++---- .../telephony/SubscriptionRepository.kt | 25 ++++++++++++++++++ .../telephony/SubscriptionRepositoryTest.kt | 26 +++++++++++++++++++ 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java index a5cdb954664..91874c4f5ac 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java +++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java @@ -41,6 +41,7 @@ import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ViewModelProvider; import androidx.preference.Preference; @@ -66,6 +67,8 @@ import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity; import com.android.settingslib.search.SearchIndexable; import com.android.settingslib.utils.ThreadUtils; +import kotlin.Unit; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -359,6 +362,16 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); collectAirplaneModeAndFinishIfOn(this); + + LifecycleOwner viewLifecycleOwner = getViewLifecycleOwner(); + new SubscriptionRepository(requireContext()) + .collectSubscriptionVisible(mSubId, viewLifecycleOwner, (isVisible) -> { + if (!isVisible) { + Log.d(LOG_TAG, "Due to subscription not visible, closes page"); + finishFragment(); + } + return Unit.INSTANCE; + }); } @Override @@ -532,11 +545,6 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme Log.d(LOG_TAG, "Set subInfo to default subInfo."); } } - if (mSubscriptionInfoEntity == null && getActivity() != null) { - // If the current subId is not existed, finish it. - finishFragment(); - return; - } onSubscriptionDetailChanged(); } } diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt index cc8c8b47b2d..26ea9b3fc42 100644 --- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt +++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt @@ -50,6 +50,31 @@ class SubscriptionRepository(private val context: Context) { fun getSelectableSubscriptionInfoList(): List = context.getSelectableSubscriptionInfoList() + /** Flow of whether the subscription visible for the given [subId]. */ + fun isSubscriptionVisibleFlow(subId: Int): Flow { + return subscriptionsChangedFlow() + .map { + val subInfo = + subscriptionManager.availableSubscriptionInfoList?.firstOrNull { subInfo -> + subInfo.subscriptionId == subId + } + subInfo != null && + SubscriptionUtil.isSubscriptionVisible(subscriptionManager, context, subInfo) + } + .conflate() + .onEach { Log.d(TAG, "[$subId] isSubscriptionVisibleFlow: $it") } + .flowOn(Dispatchers.Default) + } + + /** TODO: Move this to UI layer, when UI layer migrated to Kotlin. */ + fun collectSubscriptionVisible( + subId: Int, + lifecycleOwner: LifecycleOwner, + action: (Boolean) -> Unit, + ) { + isSubscriptionVisibleFlow(subId).collectLatestWithLifecycle(lifecycleOwner, action = action) + } + /** Flow of whether the subscription enabled for the given [subId]. */ fun isSubscriptionEnabledFlow(subId: Int): Flow { if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false) diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt index f75c14a6d37..5dbc5340745 100644 --- a/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt +++ b/tests/spa_unit/src/com/android/settings/network/telephony/SubscriptionRepositoryTest.kt @@ -189,6 +189,32 @@ class SubscriptionRepositoryTest { assertThat(subInfos.map { it.subscriptionId }).containsExactly(SUB_ID_3_NOT_IN_SLOT) } + @Test + fun isSubscriptionVisibleFlow_available_returnTrue() = runBlocking { + mockSubscriptionManager.stub { + on { getAvailableSubscriptionInfoList() } doReturn + listOf(SubscriptionInfo.Builder().apply { setId(SUB_ID_IN_SLOT_0) }.build()) + } + + val isVisible = + repository.isSubscriptionVisibleFlow(SUB_ID_IN_SLOT_0).firstWithTimeoutOrNull() + + assertThat(isVisible).isTrue() + } + + @Test + fun isSubscriptionVisibleFlow_unavailable_returnFalse() = runBlocking { + mockSubscriptionManager.stub { + on { getAvailableSubscriptionInfoList() } doReturn + listOf(SubscriptionInfo.Builder().apply { setId(SUB_ID_IN_SLOT_0) }.build()) + } + + val isVisible = + repository.isSubscriptionVisibleFlow(SUB_ID_IN_SLOT_1).firstWithTimeoutOrNull() + + assertThat(isVisible).isFalse() + } + @Test fun phoneNumberFlow() = runBlocking { mockSubscriptionManager.stub { From 49ef18e4a4de4f8c7dcd36b5f6ffa045de1e292f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Sun, 1 Sep 2024 16:40:05 +0000 Subject: [PATCH 5/7] Replace "Priority Modes" with "Modes" (Settings) Bug: 363445885 Change-Id: I3041cb2d5d8d67f5a208425393e8fdb2757c6757 Test: manual Flag: android.app.modes_ui --- res/values/strings.xml | 144 ++++++++++++++++++++--------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 56d13a22209..64bcc89279d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7979,7 +7979,7 @@ Do Not Disturb - Priority Modes + Modes Minimize distractions and take control of your attention with modes for sleep, work, driving, and everything in between. @@ -7993,18 +7993,18 @@ Set a schedule - + Schedule based on - + Day and time - + \"9 AM - 5 PM weekdays\" - + Calendar events - + {count, plural, offset:2 =0 {} @@ -8015,7 +8015,7 @@ } - + {count, plural, =0 {} @@ -8024,40 +8024,40 @@ } - + ON - + %1$s%2$s - + Not set - + Disabled - + Create a mode - + Custom - + Turn on now - + Turn off - + Mode not found Limit interruptions - + Block interruptions and distractions - + Set up %1$s @@ -8075,7 +8075,7 @@ Delete - + Rename @@ -8096,11 +8096,11 @@ When to turn on automatically - + Event schedule - + Turn on during events for - + Where invite reply is @@ -8139,9 +8139,9 @@ Allow visual signals - + Notification filters - + More settings @@ -8859,12 +8859,12 @@ It can reply to messages and take action on buttons in notifications, including snoozing or dismissing notifications and answering calls. Change settings It can turn Do Not Disturb on or off and change related settings. - It can manage and activate Priority Modes, and change related settings. + It can manage and activate Modes, and change related settings. If you turn off notification access for %1$s, Do Not Disturb access may also be turned off. - If you turn off notification access for %1$s, Priority Modes access may also be turned off. + If you turn off notification access for %1$s, Modes access may also be turned off. Turn off Cancel @@ -9042,14 +9042,14 @@ No installed apps have requested Do Not Disturb access - - Priority Modes access + + Modes access - - Allow Priority Modes access + + Allow Modes access - No installed apps have requested Priority Modes access + No installed apps have requested Modes access You haven\'t allowed notifications from this app @@ -9392,11 +9392,11 @@ other {{app_1}, {app_2}, and # more can interrupt} } - + %s (Work) Calculating\u2026 - + +%d @@ -9501,76 +9501,76 @@ Change to always interrupt - + Edit mode - + Create a mode - + Custom mode - + Mode name - + Calendar events - + Bedtime routine - + While driving - + App settings - + Info and settings in %1$s - + Managed by %1$s - + Disable %1$s? - + This mode will never turn on when disabled - + Disable - + Enable %1$s? - + This mode may turn on automatically based on its settings - + Enable - + Set a mode that follows a regular schedule - + Set a mode to sync with calendar events and invite responses - + Design a calming sleep routine. Set alarms, dim the screen, and block notifications. - + Prioritize safety on the road for a focused and distraction-free drive - + Block distractions or interruptions from your device to gain focus - + Eliminate all distractions for a quiet environment - + Personalize device experiences and settings for different users - + Minimize interruptions by only allowing important people and apps to reach you - + Set a mode that follows a regular schedule - + Keep your device in sync with your day’s events - + Wake up feeling like 100% - + Put safety first while on the road - + Gain focus to get in the zone - + For moments when courtesy counts - + Guided usage to help you stay in good hands - + Take control of your attention @@ -10172,16 +10172,16 @@ All Do Not Disturb rules created by this app will be removed. - - Allow access to Priority Modes for %1$s? + + Allow access to Modes for %1$s? - - The app will be able to turn on/off Do Not Disturb, manage and activate Priority Modes, and make changes to related settings. + + The app will be able to turn on/off Do Not Disturb, manage and activate Modes, and make changes to related settings. - - Revoke access Priority Modes for %1$s? + + Revoke access Modes for %1$s? - + All modes created by this app will be removed. From abd3889b5cf834e6c701163ea7f57ab804c60551 Mon Sep 17 00:00:00 2001 From: Yiyi Shen Date: Thu, 29 Aug 2024 18:48:03 +0800 Subject: [PATCH 6/7] [Audiosharing] Avoid audio sharing dialogs in call Test: atest Bug: 362714470 Flag: com.android.settingslib.flags.enable_le_audio_sharing Change-Id: I972d65727865793454de3eb0d9f07926d236afd7 --- .../AvailableMediaBluetoothDeviceUpdater.java | 5 +++- ...udioSharingDevicePreferenceController.java | 4 ++- .../AudioSharingDialogHandler.java | 19 +++++++++++++ .../AudioSharingDialogHandlerTest.java | 28 +++++++++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java index bb56c509373..22a39c8686a 100644 --- a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java @@ -26,6 +26,7 @@ import com.android.settings.connecteddevice.DevicePreferenceCallback; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.utils.ThreadUtils; /** Controller to maintain available media Bluetooth devices */ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater @@ -135,7 +136,9 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater @Override public boolean onPreferenceClick(Preference preference) { mMetricsFeatureProvider.logClickedPreference(preference, mMetricsCategory); - mDevicePreferenceCallback.onDeviceClick(preference); + var unused = + ThreadUtils.postOnBackgroundThread( + () -> mDevicePreferenceCallback.onDeviceClick(preference)); return true; } diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java index d6ad4bc3c7b..8b4c7f267bb 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java @@ -59,6 +59,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.android.settingslib.utils.ThreadUtils; import java.util.Locale; import java.util.concurrent.Executor; @@ -287,7 +288,8 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro if (AudioSharingUtils.isAudioSharingProfileReady(mProfileManager)) { if (!mIntentHandled.get()) { Log.d(TAG, "displayPreference: profile ready, handleDeviceClickFromIntent"); - handleDeviceClickFromIntent(); + var unused = + ThreadUtils.postOnBackgroundThread(() -> handleDeviceClickFromIntent()); mIntentHandled.set(true); } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java index 6f55a137ba2..396144ab16c 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java @@ -25,6 +25,7 @@ import android.bluetooth.BluetoothLeBroadcast; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.content.Context; +import android.media.AudioManager; import android.os.Bundle; import android.util.Log; import android.util.Pair; @@ -64,6 +65,7 @@ public class AudioSharingDialogHandler { @Nullable private final CachedBluetoothDeviceManager mDeviceManager; @Nullable private final LocalBluetoothLeBroadcast mBroadcast; @Nullable private final LocalBluetoothLeBroadcastAssistant mAssistant; + @Nullable private final AudioManager mAudioManager; private final MetricsFeatureProvider mMetricsFeatureProvider; private boolean mIsStoppingBroadcast = false; @@ -157,6 +159,7 @@ public class AudioSharingDialogHandler { mLocalBtManager != null ? mLocalBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile() : null; + mAudioManager = context.getSystemService(AudioManager.class); mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); } @@ -178,6 +181,22 @@ public class AudioSharingDialogHandler { public void handleDeviceConnected( @NonNull CachedBluetoothDevice cachedDevice, boolean userTriggered) { String anonymizedAddress = cachedDevice.getDevice().getAnonymizedAddress(); + if (mAudioManager != null) { + int audioMode = mAudioManager.getMode(); + if (audioMode == AudioManager.MODE_RINGTONE + || audioMode == AudioManager.MODE_IN_CALL + || audioMode == AudioManager.MODE_IN_COMMUNICATION) { + Log.d(TAG, "Skip handleDeviceConnected, audio mode = " + audioMode); + // TODO: add metric for this case + if (userTriggered) { + // If this method is called with user triggered, e.g. manual click on the + // "Connected devices" page, we need call setActive for the device, since user + // intend to switch active device for the call. + cachedDevice.setActive(); + } + return; + } + } boolean isBroadcasting = isBroadcasting(); boolean isLeAudioSupported = AudioSharingUtils.isLeAudioSupported(cachedDevice); if (!isLeAudioSupported) { diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandlerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandlerTest.java index 4933c430c15..ad6dd7f0754 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandlerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandlerTest.java @@ -39,6 +39,7 @@ import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothStatusCodes; import android.content.Context; import android.content.Intent; +import android.media.AudioManager; import android.os.Bundle; import android.os.Looper; import android.platform.test.flag.junit.SetFlagsRule; @@ -113,6 +114,7 @@ public class AudioSharingDialogHandlerTest { @Mock private CachedBluetoothDeviceManager mCacheManager; @Mock private LocalBluetoothLeBroadcast mBroadcast; @Mock private LocalBluetoothLeBroadcastAssistant mAssistant; + @Mock private AudioManager mAudioManager; @Mock private CachedBluetoothDevice mCachedDevice1; @Mock private CachedBluetoothDevice mCachedDevice2; @Mock private CachedBluetoothDevice mCachedDevice3; @@ -152,6 +154,8 @@ public class AudioSharingDialogHandlerTest { when(mLocalBtManager.getProfileManager()).thenReturn(mLocalBtProfileManager); when(mLocalBtProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast); when(mLocalBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant); + when(mContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager); + when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL); List bisSyncState = new ArrayList<>(); bisSyncState.add(1L); when(mState.getBisSyncState()).thenReturn(bisSyncState); @@ -187,6 +191,18 @@ public class AudioSharingDialogHandlerTest { ShadowBluetoothUtils.reset(); } + @Test + public void handleUserTriggeredDeviceConnected_inCall_setActive() { + when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); + setUpBroadcast(true); + ImmutableList deviceList = ImmutableList.of(mDevice1); + when(mAssistant.getAllConnectedDevices()).thenReturn(deviceList); + when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of()); + mHandler.handleDeviceConnected(mCachedDevice1, /* userTriggered= */ true); + shadowOf(Looper.getMainLooper()).idle(); + verify(mCachedDevice1).setActive(); + } + @Test public void handleUserTriggeredNonLeaDeviceConnected_noSharing_setActive() { setUpBroadcast(false); @@ -403,6 +419,18 @@ public class AudioSharingDialogHandlerTest { verify(mAssistant).addSource(mDevice1, mMetadata, /* isGroupOp= */ false); } + @Test + public void handleDeviceConnected_inCall_doNothing() { + when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); + setUpBroadcast(true); + when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of()); + mHandler.handleDeviceConnected(mCachedDevice2, /* userTriggered= */ false); + shadowOf(Looper.getMainLooper()).idle(); + verify(mCachedDevice2, never()).setActive(); + List childFragments = mParentFragment.getChildFragmentManager().getFragments(); + assertThat(childFragments).isEmpty(); + } + @Test public void handleNonLeaDeviceConnected_noSharing_doNothing() { setUpBroadcast(false); From b210f35a76222b3486524cca6e95f8c36ebe3b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Mon, 2 Sep 2024 18:00:36 +0200 Subject: [PATCH 7/7] Prevent "new mode" and "choose schedule" dialogs from being created without a listener This can happen if the activity is recreated; our approach doesn't support this case very well. With this change, the dialog is not reshown when the activity is recreated, which is not optimal but better than crashing (as it does today). There is no lost work because it's the first choice, and the follow-on steps (icon picker, etc) can be recreated without problems. Fixes: 359746551 Test: manual, with don't keep activities Flag: android.app.modes_ui Change-Id: I84bdeb0007e8c50ec9dd8af61991c7e55ddb8298 --- .../modes/ZenModeScheduleChooserDialog.java | 11 +++++++++++ .../modes/ZenModesListAddModeTypeChooserDialog.java | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/com/android/settings/notification/modes/ZenModeScheduleChooserDialog.java b/src/com/android/settings/notification/modes/ZenModeScheduleChooserDialog.java index d129aad6ad6..370199a6292 100644 --- a/src/com/android/settings/notification/modes/ZenModeScheduleChooserDialog.java +++ b/src/com/android/settings/notification/modes/ZenModeScheduleChooserDialog.java @@ -81,6 +81,17 @@ public class ZenModeScheduleChooserDialog extends InstrumentedDialogFragment { dialog.show(parent.getParentFragmentManager(), TAG); } + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (mOptionListener == null) { + // Probably the dialog fragment was recreated after its activity being destroyed. + // It's pointless to re-show the dialog if we can't do anything when its options are + // selected, so we don't. + dismiss(); + } + } + @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { diff --git a/src/com/android/settings/notification/modes/ZenModesListAddModeTypeChooserDialog.java b/src/com/android/settings/notification/modes/ZenModesListAddModeTypeChooserDialog.java index e7905a8f936..0bf9c5bd472 100644 --- a/src/com/android/settings/notification/modes/ZenModesListAddModeTypeChooserDialog.java +++ b/src/com/android/settings/notification/modes/ZenModesListAddModeTypeChooserDialog.java @@ -70,6 +70,17 @@ public class ZenModesListAddModeTypeChooserDialog extends InstrumentedDialogFrag dialog.show(parent.getParentFragmentManager(), TAG); } + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (mChooseModeTypeListener == null) { + // Probably the dialog fragment was recreated after its activity being destroyed. + // It's pointless to re-show the dialog if we can't do anything when its options are + // selected, so we don't. + dismiss(); + } + } + @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {