From ab6c7a8d978fe1a748525a39103b8e6316ced0c6 Mon Sep 17 00:00:00 2001 From: Yiyi Shen Date: Tue, 21 Jan 2025 15:41:34 +0800 Subject: [PATCH 01/11] [Audiosharing] Use getBroadcastToUnicastFallbackGroup to get primary Flag: com.android.settingslib.flags.adopt_primary_group_management_api_v2 Test: atest Bug: 397568136 Change-Id: I50487a8e5948fec6d8e71e2a0cf499cc7f0cf59d --- ...oSharingCallAudioPreferenceController.java | 5 +- ...dioSharingDeviceVolumeGroupController.java | 3 +- .../AudioSharingDeviceVolumePreference.java | 9 +- ...ringCallAudioPreferenceControllerTest.java | 310 ++++++++++++++++-- ...haringDeviceVolumeGroupControllerTest.java | 69 +++- ...udioSharingDeviceVolumePreferenceTest.java | 75 +++++ 6 files changed, 416 insertions(+), 55 deletions(-) diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java index 0c8b16bfab6..be861a66dd9 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java @@ -207,7 +207,7 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP (AudioSharingDeviceItem item) -> { int currentCallAudioGroupId = BluetoothUtils.getPrimaryGroupIdForBroadcast( - mContext.getContentResolver()); + mContext.getContentResolver(), mBtManager); int clickedGroupId = item.getGroupId(); if (clickedGroupId == currentCallAudioGroupId) { Log.d(TAG, "Skip set call audio device: unchanged"); @@ -414,7 +414,8 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP private Pair getActiveItemWithIndex() { List deviceItems = new ArrayList<>(mDeviceItemsInSharingSession); int fallbackActiveGroupId = - BluetoothUtils.getPrimaryGroupIdForBroadcast(mContext.getContentResolver()); + BluetoothUtils.getPrimaryGroupIdForBroadcast(mContext.getContentResolver(), + mBtManager); if (fallbackActiveGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { for (AudioSharingDeviceItem item : deviceItems) { if (item.getGroupId() == fallbackActiveGroupId) { diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java index 7b670a80fba..1659d2da2da 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java @@ -418,7 +418,8 @@ public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePre int groupId = BluetoothUtils.getGroupId(cachedDevice); // The fallback device rank first among the audio sharing device list. return (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID - && groupId == BluetoothUtils.getPrimaryGroupIdForBroadcast(mContentResolver)) + && groupId == BluetoothUtils.getPrimaryGroupIdForBroadcast(mContentResolver, + mBtManager)) ? 0 : 1; } diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreference.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreference.java index 816ec6e2cd3..944ac25941b 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreference.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreference.java @@ -46,6 +46,7 @@ public class AudioSharingDeviceVolumePreference extends SeekBarPreference { private final Context mContext; private final CachedBluetoothDevice mCachedDevice; + @Nullable private final LocalBluetoothManager mBtManager; @Nullable protected SeekBar mSeekBar; private Boolean mTrackingTouch = false; private MetricsFeatureProvider mMetricsFeatureProvider = @@ -57,6 +58,7 @@ public class AudioSharingDeviceVolumePreference extends SeekBarPreference { setLayoutResource(R.layout.preference_volume_slider); mContext = context; mCachedDevice = device; + mBtManager = Utils.getLocalBtManager(mContext); } @NonNull @@ -110,7 +112,7 @@ public class AudioSharingDeviceVolumePreference extends SeekBarPreference { if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID && groupId == BluetoothUtils.getPrimaryGroupIdForBroadcast( - mContext.getContentResolver())) { + mContext.getContentResolver(), mBtManager)) { // Set media stream volume for primary buds, audio manager will // update all buds volume in the audio sharing. setAudioManagerStreamVolume(progress); @@ -126,9 +128,8 @@ public class AudioSharingDeviceVolumePreference extends SeekBarPreference { Log.d(TAG, "Skip set device volume, device is null"); return; } - LocalBluetoothManager btManager = Utils.getLocalBtManager(mContext); - VolumeControlProfile vc = - btManager == null ? null : btManager.getProfileManager().getVolumeControlProfile(); + VolumeControlProfile vc = mBtManager == null ? null + : mBtManager.getProfileManager().getVolumeControlProfile(); if (vc != null) { vc.setDeviceVolume(device, progress, /* isGroupOp= */ true); mMetricsFeatureProvider.action( diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceControllerTest.java index 4f6fed7b732..7fece8bb2d3 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceControllerTest.java @@ -43,6 +43,8 @@ import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.os.Looper; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.view.View; @@ -78,6 +80,7 @@ import com.android.settingslib.flags.Flags; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import org.junit.After; import org.junit.Before; @@ -113,9 +116,6 @@ public class AudioSharingCallAudioPreferenceControllerTest { private static final int TEST_DEVICE_GROUP_ID1 = 1; private static final int TEST_DEVICE_GROUP_ID2 = 2; - private static final String TEST_SETTINGS_KEY = - "bluetooth_le_broadcast_fallback_active_group_id"; - @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -125,6 +125,7 @@ public class AudioSharingCallAudioPreferenceControllerTest { @Mock private BluetoothEventManager mBtEventManager; @Mock private LocalBluetoothProfileManager mBtProfileManager; @Mock private CachedBluetoothDeviceManager mCacheManager; + @Mock private LeAudioProfile mLeaProfile; @Mock private LocalBluetoothLeBroadcast mBroadcast; @Mock private LocalBluetoothLeBroadcastAssistant mAssistant; @Mock private VolumeControlProfile mVolumeControl; @@ -169,6 +170,7 @@ public class AudioSharingCallAudioPreferenceControllerTest { when(btManager.getEventManager()).thenReturn(mBtEventManager); when(btManager.getProfileManager()).thenReturn(mBtProfileManager); when(btManager.getCachedDeviceManager()).thenReturn(mCacheManager); + when(mBtProfileManager.getLeAudioProfile()).thenReturn(mLeaProfile); when(mBtProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast); when(mBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant); when(mBtProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControl); @@ -210,8 +212,8 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test + @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void onStart_flagOff_doNothing() { - mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mController.onStart(mLifecycleOwner); verify(mBtEventManager, never()).registerCallback(mController); verify(mContentResolver, never()) @@ -225,8 +227,8 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void onStart_flagOn_registerCallback() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mController.onStart(mLifecycleOwner); verify(mBtEventManager).registerCallback(mController); verify(mContentResolver) @@ -240,8 +242,8 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test + @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void onStop_flagOff_doNothing() { - mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mController.setCallbacksRegistered(true); mController.onStop(mLifecycleOwner); verify(mBtEventManager, never()).unregisterCallback(mController); @@ -251,8 +253,8 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void onStop_flagOn_notRegistered_doNothing() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mController.setCallbacksRegistered(false); mController.onStop(mLifecycleOwner); verify(mBtEventManager, never()).unregisterCallback(mController); @@ -262,8 +264,8 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void onStop_flagOn_registered_unregisterCallback() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mController.setCallbacksRegistered(true); mController.onStop(mLifecycleOwner); verify(mBtEventManager).unregisterCallback(mController); @@ -273,20 +275,20 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void getAvailabilityStatus_flagOn() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); } @Test + @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void getAvailabilityStatus_flagOff() { - mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); } @Test + @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void updateVisibility_flagOff_invisible() { - mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); when(mBroadcast.isEnabled(any())).thenReturn(true); mController.displayPreference(mScreen); mController.updateVisibility(); @@ -295,8 +297,8 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void updateVisibility_broadcastOffBluetoothOff_invisible() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); when(mBroadcast.isEnabled(any())).thenReturn(false); mShadowBluetoothAdapter.setEnabled(false); mController.displayPreference(mScreen); @@ -306,8 +308,8 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void updateVisibility_broadcastOnBluetoothOff_invisible() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); when(mBroadcast.isEnabled(any())).thenReturn(true); mShadowBluetoothAdapter.setEnabled(false); mController.displayPreference(mScreen); @@ -317,8 +319,8 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void updateVisibility_broadcastOffBluetoothOn_invisible() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); when(mBroadcast.isEnabled(any())).thenReturn(false); mController.displayPreference(mScreen); mController.updateVisibility(); @@ -327,8 +329,8 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void updateVisibility_broadcastOnBluetoothOn_visible() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); when(mBroadcast.isEnabled(any())).thenReturn(true); mController.displayPreference(mScreen); mController.updateVisibility(); @@ -337,8 +339,9 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test - public void onProfileConnectionStateChanged_noDeviceInSharing_updateSummary() { - Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID1); + @EnableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void onProfileConnectionStateChanged_adoptApi_noDeviceInSharing_updateSummary() { + when(mLeaProfile.getBroadcastToUnicastFallbackGroup()).thenReturn(TEST_DEVICE_GROUP_ID1); when(mBroadcast.isEnabled(any())).thenReturn(true); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of()); mController.displayPreference(mScreen); @@ -354,8 +357,9 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test - public void onFallbackDeviceChanged_updateSummary() { - Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID1); + @EnableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void onFallbackDeviceChanged_adoptApi_updateSummary() { + when(mLeaProfile.getBroadcastToUnicastFallbackGroup()).thenReturn(TEST_DEVICE_GROUP_ID1); when(mBroadcast.isEnabled(any())).thenReturn(true); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1)); when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState)); @@ -372,8 +376,9 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test - public void onActiveDeviceChanged_updateSummary() { - Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, + @EnableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void onActiveDeviceChanged_adoptApi_updateSummary() { + when(mLeaProfile.getBroadcastToUnicastFallbackGroup()).thenReturn( BluetoothCsipSetCoordinator.GROUP_ID_INVALID); when(mCachedDevice1.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(true); when(mBroadcast.isEnabled(any())).thenReturn(true); @@ -392,8 +397,9 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test - public void displayPreference_fallbackDeviceInSharing_showCorrectSummary() { - Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID1); + @EnableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void displayPreference_adoptApi_fallbackDeviceInSharing_showCorrectSummary() { + when(mLeaProfile.getBroadcastToUnicastFallbackGroup()).thenReturn(TEST_DEVICE_GROUP_ID1); when(mCachedDevice3.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(true); when(mBroadcast.isEnabled(any())).thenReturn(true); when(mAssistant.getAllConnectedDevices()) @@ -408,8 +414,9 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test - public void displayPreference_activeDeviceInSharing_showCorrectSummary() { - Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID2); + @EnableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void displayPreference_adoptApi_activeDeviceInSharing_showCorrectSummary() { + when(mLeaProfile.getBroadcastToUnicastFallbackGroup()).thenReturn(TEST_DEVICE_GROUP_ID2); when(mCachedDevice1.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(true); when(mBroadcast.isEnabled(any())).thenReturn(true); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1, mDevice2)); @@ -422,8 +429,9 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test - public void displayPreference_noFallbackDeviceOrActiveInSharing_showEmptySummary() { - Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID2); + @EnableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void displayPreference_adoptApi_noFallbackDeviceOrActiveInSharing_showEmptySummary() { + when(mLeaProfile.getBroadcastToUnicastFallbackGroup()).thenReturn(TEST_DEVICE_GROUP_ID2); when(mBroadcast.isEnabled(any())).thenReturn(true); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1, mDevice2)); when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState)); @@ -433,9 +441,10 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test - public void displayPreference_noFallbackOrActiveDevice_showEmptySummary() { - Settings.Secure.putInt( - mContentResolver, TEST_SETTINGS_KEY, BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + @EnableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void displayPreference_adoptApi_noFallbackOrActiveDevice_showEmptySummary() { + when(mLeaProfile.getBroadcastToUnicastFallbackGroup()).thenReturn( + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); when(mBroadcast.isEnabled(any())).thenReturn(true); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of()); mController.displayPreference(mScreen); @@ -444,13 +453,235 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test + @EnableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void displayPreference_adoptApi_clickToShowCorrectDialog() { + AlertDialog latestAlertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + if (latestAlertDialog != null) { + latestAlertDialog.dismiss(); + ShadowAlertDialogCompat.reset(); + } + when(mLeaProfile.getBroadcastToUnicastFallbackGroup()).thenReturn(TEST_DEVICE_GROUP_ID1); + mShadowBluetoothAdapter.setMostRecentlyConnectedDevices( + List.of(mDevice1, mDevice2, mDevice3)); + when(mBroadcast.isEnabled(any())).thenReturn(true); + when(mAssistant.getAllConnectedDevices()).thenReturn( + ImmutableList.of(mDevice1, mDevice2, mDevice3)); + when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState)); + mController.init(mParentFragment); + mController.displayPreference(mScreen); + shadowOf(Looper.getMainLooper()).idle(); + mPreference.performClick(); + shadowOf(Looper.getMainLooper()).idle(); + AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(dialog.isShowing()).isTrue(); + assertThat(dialog.getListView().getCount()).isEqualTo(2); + ArrayList outViews = new ArrayList<>(); + dialog.getListView() + .findViewsWithText(outViews, TEST_DEVICE_NAME1, View.FIND_VIEWS_WITH_TEXT); + assertThat(outViews.size()).isEqualTo(1); + View view = Iterables.getOnlyElement(outViews); + assertThat(view instanceof CheckedTextView).isTrue(); + assertThat(((CheckedTextView) view).isChecked()).isTrue(); + verify(mFeatureFactory.metricsFeatureProvider) + .visible( + /* context= */ eq(null), + /* source= */ anyInt(), + eq(SettingsEnums.DIALOG_AUDIO_SHARING_CALL_AUDIO), + /* latency= */ anyInt()); + } + + @Test + @EnableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void testBluetoothLeBroadcastAssistantCallbacks_adoptApi_updateSummary() { + when(mLeaProfile.getBroadcastToUnicastFallbackGroup()).thenReturn( + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + when(mBroadcast.isEnabled(any())).thenReturn(true); + when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of()); + mController.displayPreference(mScreen); + shadowOf(Looper.getMainLooper()).idle(); + assertThat(mPreference.getSummary().toString()).isEmpty(); + + // onSourceAdded will update summary + when(mLeaProfile.getBroadcastToUnicastFallbackGroup()).thenReturn(TEST_DEVICE_GROUP_ID1); + when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1)); + when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState)); + mController.mBroadcastAssistantCallback.onSourceAdded(mDevice1, /* sourceId= */ + 1, /* reason= */ 1); + shadowOf(Looper.getMainLooper()).idle(); + assertThat(mPreference.getSummary().toString()) + .isEqualTo( + mContext.getString( + R.string.audio_sharing_call_audio_description, TEST_DEVICE_NAME1)); + } + + @Test + @EnableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void testBluetoothLeBroadcastAssistantCallbacks_adoptApi_doNothing() { + when(mLeaProfile.getBroadcastToUnicastFallbackGroup()).thenReturn( + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + when(mBroadcast.isEnabled(any())).thenReturn(true); + when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of()); + mController.displayPreference(mScreen); + shadowOf(Looper.getMainLooper()).idle(); + assertThat(mPreference.getSummary().toString()).isEmpty(); + + when(mLeaProfile.getBroadcastToUnicastFallbackGroup()).thenReturn(TEST_DEVICE_GROUP_ID1); + when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1)); + when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState)); + mController.mBroadcastAssistantCallback.onSearchStarted(/* reason= */ 1); + mController.mBroadcastAssistantCallback.onSearchStartFailed(/* reason= */ 1); + mController.mBroadcastAssistantCallback.onSearchStopped(/* reason= */ 1); + mController.mBroadcastAssistantCallback.onSearchStopFailed(/* reason= */ 1); + mController.mBroadcastAssistantCallback.onSourceAddFailed( + mDevice1, mSource, /* reason= */ 1); + mController.mBroadcastAssistantCallback.onSourceRemoved( + mDevice1, /* sourceId= */ 1, /* reason= */ 1); + mController.mBroadcastAssistantCallback.onSourceRemoveFailed( + mDevice1, /* sourceId= */ 1, /* reason= */ 1); + mController.mBroadcastAssistantCallback.onSourceModified( + mDevice1, /* sourceId= */ 1, /* reason= */ 1); + mController.mBroadcastAssistantCallback.onSourceModifyFailed( + mDevice1, /* sourceId= */ 1, /* reason= */ 1); + mController.mBroadcastAssistantCallback.onSourceFound(mSource); + mController.mBroadcastAssistantCallback.onSourceLost(/* broadcastId= */ 1); + shadowOf(Looper.getMainLooper()).idle(); + mController.mBroadcastAssistantCallback.onReceiveStateChanged(mDevice1, /* sourceId= */ 1, + mState); + + // Above callbacks won't update summary. + assertThat(mPreference.getSummary().toString()).isEmpty(); + } + + @Test + @DisableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void onProfileConnectionStateChanged_noDeviceInSharing_updateSummary() { + Settings.Secure.putInt(mContentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), + TEST_DEVICE_GROUP_ID1); + when(mBroadcast.isEnabled(any())).thenReturn(true); + when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of()); + mController.displayPreference(mScreen); + shadowOf(Looper.getMainLooper()).idle(); + mPreference.setSummary("test"); + + mController.onProfileConnectionStateChanged( + mCachedDevice1, + BluetoothAdapter.STATE_DISCONNECTED, + BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); + shadowOf(Looper.getMainLooper()).idle(); + assertThat(mPreference.getSummary().toString()).isEmpty(); + } + + @Test + @DisableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void onFallbackDeviceChanged_updateSummary() { + Settings.Secure.putInt(mContentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), + TEST_DEVICE_GROUP_ID1); + when(mBroadcast.isEnabled(any())).thenReturn(true); + when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1)); + when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState)); + mController.displayPreference(mScreen); + shadowOf(Looper.getMainLooper()).idle(); + mPreference.setSummary("test"); + + mContentObserver.onChange(true); + shadowOf(Looper.getMainLooper()).idle(); + assertThat(mPreference.getSummary().toString()) + .isEqualTo( + mContext.getString( + R.string.audio_sharing_call_audio_description, TEST_DEVICE_NAME1)); + } + + @Test + @DisableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void onActiveDeviceChanged_updateSummary() { + Settings.Secure.putInt(mContentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + when(mCachedDevice1.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(true); + when(mBroadcast.isEnabled(any())).thenReturn(true); + when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1)); + when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState)); + mController.displayPreference(mScreen); + shadowOf(Looper.getMainLooper()).idle(); + mPreference.setSummary("test"); + + mController.onActiveDeviceChanged(mCachedDevice1, BluetoothProfile.LE_AUDIO); + shadowOf(Looper.getMainLooper()).idle(); + assertThat(mPreference.getSummary().toString()) + .isEqualTo( + mContext.getString( + R.string.audio_sharing_call_audio_description, TEST_DEVICE_NAME1)); + } + + @Test + @DisableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void displayPreference_fallbackDeviceInSharing_showCorrectSummary() { + Settings.Secure.putInt(mContentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), + TEST_DEVICE_GROUP_ID1); + when(mCachedDevice3.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(true); + when(mBroadcast.isEnabled(any())).thenReturn(true); + when(mAssistant.getAllConnectedDevices()) + .thenReturn(ImmutableList.of(mDevice1, mDevice2, mDevice3)); + when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState)); + mController.displayPreference(mScreen); + shadowOf(Looper.getMainLooper()).idle(); + assertThat(mPreference.getSummary().toString()) + .isEqualTo( + mContext.getString( + R.string.audio_sharing_call_audio_description, TEST_DEVICE_NAME1)); + } + + @Test + @DisableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void displayPreference_activeDeviceInSharing_showCorrectSummary() { + Settings.Secure.putInt(mContentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), + TEST_DEVICE_GROUP_ID2); + when(mCachedDevice1.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(true); + when(mBroadcast.isEnabled(any())).thenReturn(true); + when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1, mDevice2)); + when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState)); + mController.displayPreference(mScreen); + shadowOf(Looper.getMainLooper()).idle(); + assertThat(mPreference.getSummary().toString()) + .isEqualTo(mContext.getString( + R.string.audio_sharing_call_audio_description, TEST_DEVICE_NAME1)); + } + + @Test + @DisableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void displayPreference_noFallbackDeviceOrActiveInSharing_showEmptySummary() { + Settings.Secure.putInt(mContentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), + TEST_DEVICE_GROUP_ID2); + when(mBroadcast.isEnabled(any())).thenReturn(true); + when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1, mDevice2)); + when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState)); + mController.displayPreference(mScreen); + shadowOf(Looper.getMainLooper()).idle(); + assertThat(mPreference.getSummary().toString()).isEmpty(); + } + + @Test + @DisableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void displayPreference_noFallbackOrActiveDevice_showEmptySummary() { + Settings.Secure.putInt( + mContentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + when(mBroadcast.isEnabled(any())).thenReturn(true); + when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of()); + mController.displayPreference(mScreen); + shadowOf(Looper.getMainLooper()).idle(); + assertThat(mPreference.getSummary().toString()).isEmpty(); + } + + @Test + @DisableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) public void displayPreference_clickToShowCorrectDialog() { AlertDialog latestAlertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); if (latestAlertDialog != null) { latestAlertDialog.dismiss(); ShadowAlertDialogCompat.reset(); } - Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID1); + Settings.Secure.putInt(mContentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), + TEST_DEVICE_GROUP_ID1); mShadowBluetoothAdapter.setMostRecentlyConnectedDevices( List.of(mDevice1, mDevice2, mDevice3)); when(mBroadcast.isEnabled(any())).thenReturn(true); @@ -497,7 +728,8 @@ public class AudioSharingCallAudioPreferenceControllerTest { // Perform click to switch call audio device with API mSetFlagsRule.enableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API); - Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID2); + Settings.Secure.putInt(mContentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), + TEST_DEVICE_GROUP_ID2); index = listView.findIndexOfItemContainingText(TEST_DEVICE_NAME1); listView.performItemClick(index); shadowOf(Looper.getMainLooper()).idle(); @@ -548,9 +780,11 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test + @DisableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) public void testBluetoothLeBroadcastAssistantCallbacks_updateSummary() { Settings.Secure.putInt( - mContentResolver, TEST_SETTINGS_KEY, BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + mContentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); when(mBroadcast.isEnabled(any())).thenReturn(true); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of()); mController.displayPreference(mScreen); @@ -558,7 +792,8 @@ public class AudioSharingCallAudioPreferenceControllerTest { assertThat(mPreference.getSummary().toString()).isEmpty(); // onSourceAdded will update summary - Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID1); + Settings.Secure.putInt(mContentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), + TEST_DEVICE_GROUP_ID1); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1)); when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState)); mController.mBroadcastAssistantCallback.onSourceAdded(mDevice1, /* sourceId= */ @@ -571,16 +806,19 @@ public class AudioSharingCallAudioPreferenceControllerTest { } @Test + @DisableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) public void testBluetoothLeBroadcastAssistantCallbacks_doNothing() { Settings.Secure.putInt( - mContentResolver, TEST_SETTINGS_KEY, BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + mContentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); when(mBroadcast.isEnabled(any())).thenReturn(true); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of()); mController.displayPreference(mScreen); shadowOf(Looper.getMainLooper()).idle(); assertThat(mPreference.getSummary().toString()).isEmpty(); - Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID1); + Settings.Secure.putInt(mContentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), + TEST_DEVICE_GROUP_ID1); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice1)); when(mAssistant.getAllSources(any())).thenReturn(ImmutableList.of(mState)); mController.mBroadcastAssistantCallback.onSearchStarted(/* reason= */ 1); diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupControllerTest.java index a739bb3b4fb..6ca03067a15 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupControllerTest.java @@ -23,6 +23,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -42,6 +43,8 @@ import android.content.Context; import android.database.ContentObserver; import android.media.AudioManager; import android.os.Looper; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; @@ -60,6 +63,7 @@ import com.android.settings.testutils.shadow.ShadowThreadUtils; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import com.android.settingslib.bluetooth.LeAudioProfile; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -196,8 +200,8 @@ public class AudioSharingDeviceVolumeGroupControllerTest { } @Test + @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void onStart_flagOff_doNothing() { - mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mController.onStart(mLifecycleOwner); verify(mAssistant, never()) .registerServiceCallBack( @@ -214,8 +218,8 @@ public class AudioSharingDeviceVolumeGroupControllerTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void onStart_flagOn_registerCallbacks() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mController.onStart(mLifecycleOwner); verify(mAssistant) .registerServiceCallBack( @@ -229,8 +233,8 @@ public class AudioSharingDeviceVolumeGroupControllerTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void onAudioSharingProfilesConnected_flagOn_registerCallbacks() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mController.onAudioSharingProfilesConnected(); verify(mAssistant) .registerServiceCallBack( @@ -247,8 +251,8 @@ public class AudioSharingDeviceVolumeGroupControllerTest { } @Test + @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void onStop_flagOff_doNothing() { - mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mController.onStop(mLifecycleOwner); verify(mAssistant, never()) .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class)); @@ -259,8 +263,8 @@ public class AudioSharingDeviceVolumeGroupControllerTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void onStop_flagOn_callbacksNotRegistered_doNothing() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mController.setCallbacksRegistered(false); mController.onStop(mLifecycleOwner); verify(mAssistant, never()) @@ -272,8 +276,8 @@ public class AudioSharingDeviceVolumeGroupControllerTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void onStop_flagOn_callbacksRegistered_unregisterCallbacks() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mController.setCallbacksRegistered(true); mController.onStop(mLifecycleOwner); verify(mAssistant) @@ -284,16 +288,16 @@ public class AudioSharingDeviceVolumeGroupControllerTest { } @Test + @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void displayPreference_flagOff_doNothing() { - mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mController.displayPreference(mScreen); assertThat(mPreferenceGroup.isVisible()).isFalse(); verify(mDeviceUpdater, never()).forceUpdate(); } @Test + @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void displayPreference_flagOn_updateDeviceList() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mController.displayPreference(mScreen); assertThat(mPreferenceGroup.isVisible()).isFalse(); verify(mDeviceUpdater).forceUpdate(); @@ -316,6 +320,24 @@ public class AudioSharingDeviceVolumeGroupControllerTest { } @Test + @EnableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void onDeviceAdded_adoptApi_rankFallbackDeviceOnTop() { + LeAudioProfile leAudioProfile = mock(LeAudioProfile.class); + when(leAudioProfile.getBroadcastToUnicastFallbackGroup()).thenReturn(TEST_DEVICE_GROUP_ID2); + when(mProfileManager.getLeAudioProfile()).thenReturn(leAudioProfile); + when(mPreference1.getProgress()).thenReturn(TEST_VOLUME_VALUE); + when(mPreference2.getProgress()).thenReturn(TEST_VOLUME_VALUE); + mController.setPreferenceGroup(mPreferenceGroup); + mController.onDeviceAdded(mPreference1); + mController.onDeviceAdded(mPreference2); + shadowOf(Looper.getMainLooper()).idle(); + + verify(mPreference1).setOrder(1); + verify(mPreference2).setOrder(0); + } + + @Test + @DisableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) public void onDeviceAdded_rankFallbackDeviceOnTop() { Settings.Secure.putInt( mContentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), @@ -374,8 +396,8 @@ public class AudioSharingDeviceVolumeGroupControllerTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void updateVisibility_emptyPreferenceGroup_doNothing() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mController.setCallbacksRegistered(true); mController.updateVisibility(); shadowOf(Looper.getMainLooper()).idle(); @@ -384,8 +406,8 @@ public class AudioSharingDeviceVolumeGroupControllerTest { } @Test + @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void updateVisibility_flagOff_setVisibleToFalse() { - mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mController.setCallbacksRegistered(true); mPreferenceGroup.addPreference(mPreference1); when(mBroadcast.isEnabled(null)).thenReturn(true); @@ -399,8 +421,8 @@ public class AudioSharingDeviceVolumeGroupControllerTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void updateVisibility_notEmptyPreferenceGroup_noSharing_setVisibleToFalse() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mController.setCallbacksRegistered(true); mPreferenceGroup.addPreference(mPreference1); when(mBroadcast.isEnabled(null)).thenReturn(false); @@ -414,8 +436,8 @@ public class AudioSharingDeviceVolumeGroupControllerTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) public void updateVisibility_notEmptyPreferenceGroup_isSharing_setVisibleToTrue() { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mController.setCallbacksRegistered(true); mPreferenceGroup.addPreference(mPreference1); when(mBroadcast.isEnabled(null)).thenReturn(true); @@ -429,6 +451,29 @@ public class AudioSharingDeviceVolumeGroupControllerTest { } @Test + @EnableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void settingsObserverOnChange_adoptApi_updatePreferenceOrder() { + LeAudioProfile leAudioProfile = mock(LeAudioProfile.class); + when(leAudioProfile.getBroadcastToUnicastFallbackGroup()).thenReturn(TEST_DEVICE_GROUP_ID2); + when(mProfileManager.getLeAudioProfile()).thenReturn(leAudioProfile); + when(mPreference1.getProgress()).thenReturn(TEST_VOLUME_VALUE); + when(mPreference2.getProgress()).thenReturn(TEST_VOLUME_VALUE); + mController.setPreferenceGroup(mPreferenceGroup); + mController.onDeviceAdded(mPreference1); + mController.onDeviceAdded(mPreference2); + shadowOf(Looper.getMainLooper()).idle(); + + + when(leAudioProfile.getBroadcastToUnicastFallbackGroup()).thenReturn(TEST_DEVICE_GROUP_ID1); + mContentObserver.onChange(true); + shadowOf(Looper.getMainLooper()).idle(); + + verify(mPreference1).setOrder(0); + verify(mPreference2).setOrder(1); + } + + @Test + @DisableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) public void settingsObserverOnChange_updatePreferenceOrder() { Settings.Secure.putInt( mContentResolver, BluetoothUtils.getPrimaryGroupIdUriForBroadcast(), diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreferenceTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreferenceTest.java index 5ff143fa1dc..c84d7f8184d 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreferenceTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreferenceTest.java @@ -31,6 +31,9 @@ import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.media.AudioManager; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.widget.SeekBar; @@ -41,9 +44,11 @@ import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.ShadowBluetoothUtils; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LeAudioProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.bluetooth.VolumeControlProfile; +import com.android.settingslib.flags.Flags; import org.junit.Before; import org.junit.Rule; @@ -64,9 +69,11 @@ public class AudioSharingDeviceVolumePreferenceTest { private static final int TEST_MIN_STREAM_VALUE = 0; @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Mock private LocalBluetoothManager mLocalBtManager; @Mock private LocalBluetoothProfileManager mLocalBtProfileManager; + @Mock private LeAudioProfile mLeAudioProfile; @Mock private VolumeControlProfile mVolumeControl; @Mock private CachedBluetoothDevice mCachedDevice; @Mock private BluetoothDevice mDevice; @@ -84,6 +91,7 @@ public class AudioSharingDeviceVolumePreferenceTest { mFeatureFactory = FakeFeatureFactory.setupForTest(); when(mLocalBtManager.getProfileManager()).thenReturn(mLocalBtProfileManager); when(mLocalBtProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControl); + when(mLocalBtProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile); when(mContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager); when(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) .thenReturn(TEST_MAX_STREAM_VALUE); @@ -161,6 +169,70 @@ public class AudioSharingDeviceVolumePreferenceTest { } @Test + @EnableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void onStopTrackingTouch_adoptApi_fallbackDevice_setDeviceVolume() { + when(mLeAudioProfile.getBroadcastToUnicastFallbackGroup()).thenReturn(TEST_DEVICE_GROUP_ID); + mPreference.onStopTrackingTouch(mSeekBar); + + verifyNoInteractions(mVolumeControl); + verify(mAudioManager) + .setStreamVolume(AudioManager.STREAM_MUSIC, TEST_MAX_STREAM_VALUE, /* flags= */ 0); + verify(mFeatureFactory.metricsFeatureProvider) + .action( + mContext, + SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME, + /* isPrimary= */ true); + } + + @Test + @EnableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void onProgressChanged_adoptApi_fallbackDevice_fromUserNotInTouch_setDeviceVolume() { + when(mLeAudioProfile.getBroadcastToUnicastFallbackGroup()).thenReturn(TEST_DEVICE_GROUP_ID); + mPreference.onProgressChanged(mSeekBar, TEST_VOLUME_VALUE, /* fromUser= */ true); + + verifyNoInteractions(mVolumeControl); + verify(mAudioManager) + .setStreamVolume(AudioManager.STREAM_MUSIC, TEST_MAX_STREAM_VALUE, /* flags= */ 0); + verify(mFeatureFactory.metricsFeatureProvider) + .action( + mContext, + SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME, + /* isPrimary= */ true); + } + + @Test + @EnableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void onProgressChanged_adoptApi_fallbackDevice_fromUserInTouch_doNothing() { + when(mLeAudioProfile.getBroadcastToUnicastFallbackGroup()).thenReturn(TEST_DEVICE_GROUP_ID); + mPreference.onStartTrackingTouch(mSeekBar); + mPreference.onProgressChanged(mSeekBar, TEST_VOLUME_VALUE, /* fromUser= */ true); + + verifyNoInteractions(mVolumeControl); + verifyNoInteractions(mAudioManager); + verify(mFeatureFactory.metricsFeatureProvider, never()) + .action( + any(Context.class), + eq(SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME), + anyBoolean()); + } + + @Test + @EnableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) + public void onProgressChanged_adoptApi_fallbackDevice_notFromUserNotInTouch_doNothing() { + when(mLeAudioProfile.getBroadcastToUnicastFallbackGroup()).thenReturn(TEST_DEVICE_GROUP_ID); + mPreference.onProgressChanged(mSeekBar, TEST_VOLUME_VALUE, /* fromUser= */ false); + + verifyNoInteractions(mVolumeControl); + verifyNoInteractions(mAudioManager); + verify(mFeatureFactory.metricsFeatureProvider, never()) + .action( + any(Context.class), + eq(SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME), + anyBoolean()); + } + + @Test + @DisableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) public void onStopTrackingTouch_fallbackDevice_setDeviceVolume() { Settings.Secure.putInt( mContext.getContentResolver(), @@ -179,6 +251,7 @@ public class AudioSharingDeviceVolumePreferenceTest { } @Test + @DisableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) public void onProgressChanged_fallbackDevice_fromUserNotInTouch_setDeviceVolume() { Settings.Secure.putInt( mContext.getContentResolver(), @@ -197,6 +270,7 @@ public class AudioSharingDeviceVolumePreferenceTest { } @Test + @DisableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) public void onProgressChanged_fallbackDevice_fromUserInTouch_doNothing() { Settings.Secure.putInt( mContext.getContentResolver(), @@ -215,6 +289,7 @@ public class AudioSharingDeviceVolumePreferenceTest { } @Test + @DisableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API_V2) public void onProgressChanged_fallbackDevice_notFromUserNotInTouch_doNothing() { Settings.Secure.putInt( mContext.getContentResolver(), From 5d7dfdea71bdf63b62eb0a154228357c1d241b63 Mon Sep 17 00:00:00 2001 From: noshinmir Date: Mon, 24 Feb 2025 09:18:34 +0000 Subject: [PATCH 02/11] Add Wi-Fi and adaptive mobile network (5G PM) toggle event in Adaptive Connectivity UX Bug: 393645580 Flag: com.android.settings.flags.enable_nested_toggle_switches Test: Manual testing atest AdaptiveConnectivityScreenTest atest AdaptiveMobileNetworkTogglePreferenceTest atest WifiScorerTogglePreferenceTest Change-Id: Ic3b8e4aca5e2096b4e94aed10cd516c3f94e48c1 --- .../network/AdaptiveConnectivityScreen.kt | 6 +- .../network/AdaptiveConnectivitySettings.java | 29 ++-- .../AdaptiveMobileNetworkTogglePreference.kt | 89 ++++++++++++ .../network/WifiScorerTogglePreference.kt | 101 +++++++++++++ .../network/AdaptiveConnectivityScreenTest.kt | 136 ++++++++++++++++-- ...aptiveMobileNetworkTogglePreferenceTest.kt | 93 ++++++++++++ .../network/WifiScorerTogglePreferenceTest.kt | 104 ++++++++++++++ 7 files changed, 533 insertions(+), 25 deletions(-) create mode 100644 src/com/android/settings/network/AdaptiveMobileNetworkTogglePreference.kt create mode 100644 src/com/android/settings/network/WifiScorerTogglePreference.kt create mode 100644 tests/robotests/src/com/android/settings/network/AdaptiveMobileNetworkTogglePreferenceTest.kt create mode 100644 tests/robotests/src/com/android/settings/network/WifiScorerTogglePreferenceTest.kt diff --git a/src/com/android/settings/network/AdaptiveConnectivityScreen.kt b/src/com/android/settings/network/AdaptiveConnectivityScreen.kt index 829ec8dc534..8d3878db683 100644 --- a/src/com/android/settings/network/AdaptiveConnectivityScreen.kt +++ b/src/com/android/settings/network/AdaptiveConnectivityScreen.kt @@ -37,6 +37,10 @@ class AdaptiveConnectivityScreen : PreferenceScreenCreator { override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(context, this) { +AdaptiveConnectivityTogglePreference() + if (Flags.enableNestedToggleSwitches()) { + +WifiScorerTogglePreference() + +AdaptiveMobileNetworkTogglePreference() + } } override fun hasCompleteHierarchy() = false @@ -44,4 +48,4 @@ class AdaptiveConnectivityScreen : PreferenceScreenCreator { companion object { const val KEY = "adaptive_connectivity" } -} +} \ No newline at end of file diff --git a/src/com/android/settings/network/AdaptiveConnectivitySettings.java b/src/com/android/settings/network/AdaptiveConnectivitySettings.java index a099d79f007..20bb73a464e 100644 --- a/src/com/android/settings/network/AdaptiveConnectivitySettings.java +++ b/src/com/android/settings/network/AdaptiveConnectivitySettings.java @@ -15,9 +15,14 @@ */ package com.android.settings.network; +import static android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED; +import static android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED; + import android.app.settings.SettingsEnums; import android.content.Context; +import android.net.wifi.WifiManager; import android.os.Bundle; +import android.provider.Settings; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -31,12 +36,7 @@ import com.android.settingslib.search.SearchIndexable; /** Adaptive connectivity is a feature which automatically manages network connections. */ @SearchIndexable public class AdaptiveConnectivitySettings extends DashboardFragment { - private static final String TAG = "AdaptiveConnectivitySettings"; - protected static final String ADAPTIVE_CONNECTIVITY_WIFI_ENABLED = - "adaptive_connectivity_wifi_enabled"; - protected static final String ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED = - "adaptive_connectivity_mobile_network_enabled"; @Override public int getMetricsCategory() { @@ -65,16 +65,25 @@ public class AdaptiveConnectivitySettings extends DashboardFragment { public void onCreatePreferences(@NonNull Bundle savedInstanceState, @NonNull String rootKey) { Log.i("Settings", "onCreatePreferences"); super.onCreatePreferences(savedInstanceState, rootKey); - if (Flags.enableNestedToggleSwitches()) { - setSwitchVisibility(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED, true); - setSwitchVisibility(ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED, true); + if (Flags.enableNestedToggleSwitches() && !isCatalystEnabled()) { + setupSwitchPreferenceCompat(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED); + setupSwitchPreferenceCompat(ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED); } } - private void setSwitchVisibility(String key, boolean isVisible) { + private void setupSwitchPreferenceCompat(String key) { SwitchPreferenceCompat switchPreference = findPreference(key); if (switchPreference != null) { - switchPreference.setVisible(isVisible); + switchPreference.setOnPreferenceChangeListener( + (preference, newValue) -> { + boolean isChecked = (Boolean) newValue; + Settings.Secure.putInt(getContentResolver(), key, isChecked ? 1 : 0); + if (preference.getKey().equals(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED)) { + getSystemService(WifiManager.class).setWifiScoringEnabled(isChecked); + } + return true; + }); + switchPreference.setVisible(true); } } } diff --git a/src/com/android/settings/network/AdaptiveMobileNetworkTogglePreference.kt b/src/com/android/settings/network/AdaptiveMobileNetworkTogglePreference.kt new file mode 100644 index 00000000000..2b6ec916dc3 --- /dev/null +++ b/src/com/android/settings/network/AdaptiveMobileNetworkTogglePreference.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2025 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.network + +import android.app.settings.SettingsEnums.ACTION_ADAPTIVE_CONNECTIVITY +import android.content.Context +import android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED +import com.android.settings.R +import com.android.settings.contract.KEY_ADAPTIVE_CONNECTIVITY +import com.android.settings.metrics.PreferenceActionMetricsProvider +import com.android.settingslib.datastore.KeyValueStore +import com.android.settingslib.datastore.KeyValueStoreDelegate +import com.android.settingslib.datastore.SettingsSecureStore +import com.android.settingslib.datastore.SettingsStore +import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel +import com.android.settingslib.metadata.SwitchPreference + +class AdaptiveMobileNetworkTogglePreference() : + SwitchPreference( + KEY, + R.string.adaptive_connectivity_mobile_network_switch_title, + ), + PreferenceActionMetricsProvider { + + override val preferenceActionMetrics: Int + get() = ACTION_ADAPTIVE_CONNECTIVITY + + override val key: String + get() = KEY + + override fun tags(context: Context) = arrayOf(KEY_ADAPTIVE_CONNECTIVITY) + + override fun storage(context: Context): KeyValueStore = + AdaptiveMobileNetworkToggleStorage(context) + + override fun getReadPermissions(context: Context) = SettingsSecureStore.getReadPermissions() + + override fun getWritePermissions(context: Context) = SettingsSecureStore.getWritePermissions() + + override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) = + ReadWritePermit.ALLOW + + override fun getWritePermit( + context: Context, + value: Boolean?, + callingPid: Int, + callingUid: Int, + ) = ReadWritePermit.ALLOW + + override val sensitivityLevel + get() = SensitivityLevel.NO_SENSITIVITY + + @Suppress("UNCHECKED_CAST") + private class AdaptiveMobileNetworkToggleStorage( + private val context: Context, + private val settingsStore: SettingsStore = SettingsSecureStore.get(context), + ) : KeyValueStoreDelegate { + + override val keyValueStoreDelegate + get() = settingsStore + + override fun getDefaultValue(key: String, valueType: Class) = + DEFAULT_VALUE as T + + override fun setValue(key: String, valueType: Class, value: T?) { + settingsStore.setValue(key, valueType, value) + } + } + + companion object { + const val KEY = ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED + const val DEFAULT_VALUE = true + } +} \ No newline at end of file diff --git a/src/com/android/settings/network/WifiScorerTogglePreference.kt b/src/com/android/settings/network/WifiScorerTogglePreference.kt new file mode 100644 index 00000000000..b94f6217dad --- /dev/null +++ b/src/com/android/settings/network/WifiScorerTogglePreference.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2025 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.network + +import android.Manifest +import android.app.settings.SettingsEnums.ACTION_ADAPTIVE_CONNECTIVITY +import android.content.Context +import android.net.wifi.WifiManager +import android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED +import androidx.annotation.RequiresPermission +import com.android.settings.R +import com.android.settings.contract.KEY_ADAPTIVE_CONNECTIVITY +import com.android.settings.metrics.PreferenceActionMetricsProvider +import com.android.settingslib.datastore.KeyValueStore +import com.android.settingslib.datastore.KeyValueStoreDelegate +import com.android.settingslib.datastore.SettingsSecureStore +import com.android.settingslib.datastore.SettingsStore +import com.android.settingslib.datastore.and +import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel +import com.android.settingslib.metadata.SwitchPreference + +class WifiScorerTogglePreference() : + SwitchPreference( + KEY, + R.string.adaptive_connectivity_wifi_switch_title + ), + PreferenceActionMetricsProvider { + + override val preferenceActionMetrics: Int + get() = ACTION_ADAPTIVE_CONNECTIVITY + + override val key: String + get() = KEY + + override fun tags(context: Context) = arrayOf(KEY_ADAPTIVE_CONNECTIVITY) + + override fun storage(context: Context): KeyValueStore = + WifiScorerToggleStorage(context) + + override fun getReadPermissions(context: Context) = SettingsSecureStore.getReadPermissions() + + override fun getWritePermissions(context: Context) = + SettingsSecureStore.getWritePermissions() and Manifest.permission.NETWORK_SETTINGS + + override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) = + ReadWritePermit.ALLOW + + override fun getWritePermit( + context: Context, + value: Boolean?, + callingPid: Int, + callingUid: Int, + ) = ReadWritePermit.ALLOW + + override val sensitivityLevel + get() = SensitivityLevel.NO_SENSITIVITY + + @Suppress("UNCHECKED_CAST") + private class WifiScorerToggleStorage( + private val context: Context, + private val settingsStore: SettingsStore = SettingsSecureStore.get(context), + ) : KeyValueStoreDelegate { + + override val keyValueStoreDelegate + get() = settingsStore + + override fun getDefaultValue(key: String, valueType: Class) = + DEFAULT_VALUE as T + + @RequiresPermission(Manifest.permission.NETWORK_SETTINGS) + override fun setValue(key: String, valueType: Class, value: T?) { + settingsStore.setValue(key, valueType, value) + context + .getSystemService(WifiManager::class.java) + ?.setWifiScoringEnabled( + (value as Boolean?) + ?: DEFAULT_VALUE + ) + } + } + + companion object { + const val KEY = ADAPTIVE_CONNECTIVITY_WIFI_ENABLED + const val DEFAULT_VALUE = true + } +} \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/network/AdaptiveConnectivityScreenTest.kt b/tests/robotests/src/com/android/settings/network/AdaptiveConnectivityScreenTest.kt index 2b3173a051c..53f37c90164 100644 --- a/tests/robotests/src/com/android/settings/network/AdaptiveConnectivityScreenTest.kt +++ b/tests/robotests/src/com/android/settings/network/AdaptiveConnectivityScreenTest.kt @@ -16,11 +16,18 @@ package com.android.settings.network +import android.content.Context +import android.platform.test.annotations.EnableFlags +import android.provider.Settings +import android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED +import android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED +import android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED +import androidx.fragment.app.testing.launchFragmentInContainer import androidx.preference.SwitchPreferenceCompat +import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.flags.Flags -import com.android.settings.network.AdaptiveConnectivitySettings.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED -import com.android.settings.network.AdaptiveConnectivitySettings.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED +import com.android.settingslib.metadata.PreferenceHierarchy import com.android.settingslib.preference.CatalystScreenTestCase import com.google.common.truth.Truth.assertThat import org.junit.Test @@ -28,11 +35,12 @@ import org.junit.runner.RunWith @Suppress("DEPRECATION") @RunWith(AndroidJUnit4::class) -class AdaptiveConnectivityScreenTest : CatalystScreenTestCase() { +class AdaptiveConnectivityScreenTest() : CatalystScreenTestCase() { override val preferenceScreenCreator = AdaptiveConnectivityScreen() override val flagName get() = Flags.FLAG_CATALYST_ADAPTIVE_CONNECTIVITY - + private lateinit var fragment: AdaptiveConnectivitySettings + private val mContext: Context = ApplicationProvider.getApplicationContext() override fun migration() {} @Test @@ -41,21 +49,121 @@ class AdaptiveConnectivityScreenTest : CatalystScreenTestCase() { } @Test - fun flagDefaultDisabled_noSwitchPreferenceCompatExists() { - // create fragment - val fragment: AdaptiveConnectivitySettings = - preferenceScreenCreator.fragmentClass().newInstance() - // check if switch preference exists - assertSwitchPreferenceCompatIsNull(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED, fragment) - assertSwitchPreferenceCompatIsNull(ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED, fragment) + fun getPreferenceHierarchy_returnsHierarchy() { + val hierarchy: PreferenceHierarchy = + preferenceScreenCreator.getPreferenceHierarchy(mContext) + (appContext) + assertThat(hierarchy.find(ADAPTIVE_CONNECTIVITY_ENABLED)).isNotNull() + assertThat(hierarchy.find(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED)).isNull() + assertThat(hierarchy.find(ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED)).isNull() } - private fun assertSwitchPreferenceCompatIsNull( + @Test + @EnableFlags(Flags.FLAG_ENABLE_NESTED_TOGGLE_SWITCHES) + fun getPreferenceHierarchy_flagEnabled_returnsHierarchyWithNestedToggle() { + val hierarchy: PreferenceHierarchy = + preferenceScreenCreator.getPreferenceHierarchy(mContext) + (appContext) + assertThat(hierarchy.find(ADAPTIVE_CONNECTIVITY_ENABLED)).isNotNull() + assertThat(hierarchy.find(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED)).isNotNull() + assertThat(hierarchy.find(ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED)).isNotNull() + + } + + @Test + fun flagDefaultDisabled_noSwitchPreferenceCompatExists() { + val scenario = launchFragmentInContainer() + scenario.onFragment { fragment -> + this.fragment = fragment + assertSwitchPreferenceCompatVisibility( + ADAPTIVE_CONNECTIVITY_WIFI_ENABLED, fragment, + false + ) + assertSwitchPreferenceCompatVisibility( + ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED, + fragment, + false + ) + } + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_NESTED_TOGGLE_SWITCHES) + fun flagEnabled_switchPreferenceCompatExists() { + val scenario = launchFragmentInContainer() + scenario.onFragment { fragment -> + this.fragment = fragment + assertSwitchPreferenceCompatVisibility( + ADAPTIVE_CONNECTIVITY_WIFI_ENABLED, fragment, + true + ) + assertSwitchPreferenceCompatVisibility( + ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED, + fragment, + true + ) + } + } + + + @Test + @EnableFlags(Flags.FLAG_ENABLE_NESTED_TOGGLE_SWITCHES) + fun flagEnabled_onWifiScorerSwitchClick_shouldUpdateSetting() { + val scenario = launchFragmentInContainer() + scenario.onFragment { fragment: AdaptiveConnectivitySettings -> + this.fragment = fragment + val switchPreference = + fragment.findPreference(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED) + assertThat(switchPreference?.isChecked).isTrue() + switchPreference?.performClick() + assertThat(switchPreference?.isChecked).isFalse() + assertThat(updateSetting(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED)).isFalse() + switchPreference?.performClick() + assertThat(switchPreference?.isChecked).isTrue() + assertThat(updateSetting(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED)).isTrue() + } + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_NESTED_TOGGLE_SWITCHES) + fun flagEnabled_onAdaptiveMobileNetworkSwitchClick_shouldUpdateSetting() { + val scenario = launchFragmentInContainer() + scenario.onFragment { fragment: AdaptiveConnectivitySettings -> + this.fragment = fragment + val switchPreference = + fragment.findPreference( + ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED + ) + assertThat(switchPreference?.isChecked).isTrue() + switchPreference?.performClick() + assertThat(switchPreference?.isChecked).isFalse() + assertThat(updateSetting(ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED)).isFalse() + switchPreference?.performClick() + assertThat(switchPreference?.isChecked).isTrue() + assertThat(updateSetting(ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED)).isTrue() + } + } + + /** + * Helper function to get the setting value from Settings.Secure. + * + * @param key the key of the setting to get. + */ + private fun updateSetting(key: String): Boolean { + return (Settings.Secure.getInt( + mContext.contentResolver, + key, + 0 + ) == 1) + } + + private fun assertSwitchPreferenceCompatVisibility( key: String, - fragment: AdaptiveConnectivitySettings + fragment: AdaptiveConnectivitySettings, + isVisible: Boolean ) { val switchPreference = fragment.findPreference(key) - assertThat(switchPreference).isNull() + assertThat(switchPreference?.isVisible).isEqualTo(isVisible) } } diff --git a/tests/robotests/src/com/android/settings/network/AdaptiveMobileNetworkTogglePreferenceTest.kt b/tests/robotests/src/com/android/settings/network/AdaptiveMobileNetworkTogglePreferenceTest.kt new file mode 100644 index 00000000000..6b2b7dde2ae --- /dev/null +++ b/tests/robotests/src/com/android/settings/network/AdaptiveMobileNetworkTogglePreferenceTest.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2025 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.network + +import android.content.Context +import android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED +import androidx.preference.SwitchPreferenceCompat +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.preference.createAndBindWidget +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class AdaptiveMobileNetworkTogglePreferenceTest { + private val context: Context = ApplicationProvider.getApplicationContext() + + private val adaptiveMobileNetworkTogglePreference = AdaptiveMobileNetworkTogglePreference() + + @Test + fun switchClick_defaultDisabled_returnFalse() { + setAdaptiveMobileNetworkEnabled(false) + + assertThat(getSwitchPreference().isChecked).isFalse() + } + + @Test + fun switchClick_defaultEnabled_returnTrue() { + setAdaptiveMobileNetworkEnabled(true) + + assertThat(getSwitchPreference().isChecked).isTrue() + } + + @Test + fun setChecked_defaultEnabled_updatesCorrectly() { + val preference = getSwitchPreference() + assertThat(preference.isChecked).isTrue() + + preference.performClick() + + assertThat(preference.isChecked).isFalse() + + preference.performClick() + + assertThat(preference.isChecked).isTrue() + } + + @Test + fun storeSetTrue_setAdaptiveMobileNetworkEnabled() { + setAdaptiveMobileNetworkEnabled(true) + + assertThat( + getAdaptiveMobileNetworkEnabled() + ).isTrue() + } + + @Test + fun storeSetFalse_setAdaptiveMobileNetworkDisabled() { + setAdaptiveMobileNetworkEnabled(false) + + assertThat( + getAdaptiveMobileNetworkEnabled() + ).isFalse() + } + + private fun getSwitchPreference(): SwitchPreferenceCompat = + adaptiveMobileNetworkTogglePreference.createAndBindWidget(context) + + private fun setAdaptiveMobileNetworkEnabled(enabled: Boolean) = + adaptiveMobileNetworkTogglePreference + .storage(context) + .setBoolean(ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED, enabled) + + private fun getAdaptiveMobileNetworkEnabled() = + adaptiveMobileNetworkTogglePreference + .storage(context) + .getBoolean(ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED) +} diff --git a/tests/robotests/src/com/android/settings/network/WifiScorerTogglePreferenceTest.kt b/tests/robotests/src/com/android/settings/network/WifiScorerTogglePreferenceTest.kt new file mode 100644 index 00000000000..794b5eb2401 --- /dev/null +++ b/tests/robotests/src/com/android/settings/network/WifiScorerTogglePreferenceTest.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2025 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.network + +import android.content.Context +import android.content.ContextWrapper +import android.net.wifi.WifiManager +import android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED +import androidx.preference.SwitchPreferenceCompat +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.preference.createAndBindWidget +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +@RunWith(AndroidJUnit4::class) +class WifiScorerTogglePreferenceTest { + private val mockWifiManager = mock() + + private val context: Context = + object : ContextWrapper(ApplicationProvider.getApplicationContext()) { + override fun getSystemService(name: String): Any? = + when { + name == getSystemServiceName(WifiManager::class.java) -> mockWifiManager + else -> super.getSystemService(name) + } + } + + private val wifiScorerTogglePreference = WifiScorerTogglePreference() + + @Test + fun switchClick_defaultDisabled_returnFalse() { + setWifiScorerEnabled(false) + + assertThat(getSwitchPreference().isChecked).isFalse() + } + + @Test + fun switchClick_defaultEnabled_returnTrue() { + setWifiScorerEnabled(true) + + assertThat(getSwitchPreference().isChecked).isTrue() + } + + @Test + fun setChecked_defaultEnabled_updatesCorrectly() { + val preference = getSwitchPreference() + assertThat(preference.isChecked).isTrue() + + preference.performClick() + + assertThat(preference.isChecked).isFalse() + + preference.performClick() + + assertThat(preference.isChecked).isTrue() + } + + @Test + fun storeSetTrue_wifiManagerSetWifiScoringEnabled() { + setWifiScorerEnabled(true) + + assertThat(getWifiScorerEnabled()).isTrue() + verify(mockWifiManager).setWifiScoringEnabled(true) + } + + @Test + fun storeSetFalse_wifiManagerSetWifiScoringDisabled() { + setWifiScorerEnabled(false) + + assertThat(getWifiScorerEnabled()).isFalse() + verify(mockWifiManager).setWifiScoringEnabled(false) + } + + private fun getSwitchPreference(): SwitchPreferenceCompat = + wifiScorerTogglePreference.createAndBindWidget(context) + + private fun setWifiScorerEnabled(enabled: Boolean) = + wifiScorerTogglePreference + .storage(context) + .setBoolean(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED, enabled) + + private fun getWifiScorerEnabled() = + wifiScorerTogglePreference + .storage(context) + .getBoolean(ADAPTIVE_CONNECTIVITY_WIFI_ENABLED) +} From 3b32efc31652508d2d9b8c93e0037a69b68e4525 Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Fri, 14 Mar 2025 21:43:16 +0800 Subject: [PATCH 03/11] Remove hardcoded color for dynamic items Bug: 403063643 Test: visual Flag: com.android.settingslib.widget.theme.flags.is_expressive_design_enabled Change-Id: I42bbcc6a155feb1fd6576af5b1aac0d8fd07583a --- res/values/colors.xml | 2 -- .../settings/dashboard/DashboardFeatureProviderImpl.java | 4 ---- 2 files changed, 6 deletions(-) diff --git a/res/values/colors.xml b/res/values/colors.xml index 9c9a29c79f9..af7b1320b05 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -84,8 +84,6 @@ @color/homepage_cyan_bg @color/homepage_cyan_fg @color/homepage_cyan_bg - @color/homepage_cyan_fg - @color/homepage_cyan_bg @color/homepage_cyan_fg @color/homepage_cyan_bg @color/homepage_red_fg diff --git a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java index f61fbb6d13a..4fd8a211c35 100644 --- a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java +++ b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java @@ -507,10 +507,6 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { R.dimen.dashboard_tile_image_size); drawable.setLayerSize(0, size, size); return drawable; - } else if (TextUtils.equals(tile.getPackageName(), - mPackageManager.getWellbeingPackageName())) { - return getRoundedIcon(iconDrawable, - R.color.homepage_wellbeing_foreground, R.color.homepage_wellbeing_background); } Pair colors = getSchemedColors(tile); From 526dacdc824b5a58bd7aa91ef36c6d83440172c5 Mon Sep 17 00:00:00 2001 From: Candice Date: Fri, 14 Mar 2025 16:13:24 +0000 Subject: [PATCH 04/11] Move the content description for Display size and text preview to xml Bug: 395882764 Test: atest TextReadingPreviewControllerTest Test: atest TextReadingPreviewPreferenceTest Flag: EXEMPT BUGFIX Change-Id: I070ec2b9b39a205fd3a97636f0c45fb50b670049 --- .../accessibility_text_reading_preview.xml | 1 - ...sibility_text_reading_preview_app_grid.xml | 21 +++++---- ...lity_text_reading_preview_mail_content.xml | 6 +-- res/layout/screen_zoom_preview_1.xml | 5 ++- res/values/config.xml | 12 +---- .../TextReadingPreviewController.java | 16 ------- .../TextReadingPreviewPreference.java | 44 ++++++++----------- .../TextReadingPreviewControllerTest.java | 11 ----- .../TextReadingPreviewPreferenceTest.java | 32 +++----------- 9 files changed, 44 insertions(+), 104 deletions(-) diff --git a/res/layout/accessibility_text_reading_preview.xml b/res/layout/accessibility_text_reading_preview.xml index 2532a7957eb..252736efa0b 100644 --- a/res/layout/accessibility_text_reading_preview.xml +++ b/res/layout/accessibility_text_reading_preview.xml @@ -46,7 +46,6 @@ android:id="@+id/preview_pager" android:layout_width="wrap_content" android:layout_height="217dp" - android:contentDescription="@string/preview_pager_content_description" android:nestedScrollingEnabled="true" /> - - + android:contentDescription="@string/preview_pager_home_content_description" > + + diff --git a/res/layout/accessibility_text_reading_preview_mail_content.xml b/res/layout/accessibility_text_reading_preview_mail_content.xml index e55d3891b38..db5ee7dc91b 100644 --- a/res/layout/accessibility_text_reading_preview_mail_content.xml +++ b/res/layout/accessibility_text_reading_preview_mail_content.xml @@ -14,18 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. --> - + android:contentDescription="@string/preview_pager_email_content_description"> + android:orientation="vertical" + android:importantForAccessibility="noHideDescendants"> + android:contentDescription="@string/preview_pager_message_content_description"> + android:layout_height="wrap_content" + android:importantForAccessibility="noHideDescendants"> - + @layout/accessibility_text_reading_preview_app_grid @layout/screen_zoom_preview_1 @layout/accessibility_text_reading_preview_mail_content - - - @string/preview_pager_home_content_description - @string/preview_pager_message_content_description - @string/preview_pager_email_content_description - - com.android.vending diff --git a/src/com/android/settings/accessibility/TextReadingPreviewController.java b/src/com/android/settings/accessibility/TextReadingPreviewController.java index e268aaa981f..99f1f3fa0c3 100644 --- a/src/com/android/settings/accessibility/TextReadingPreviewController.java +++ b/src/com/android/settings/accessibility/TextReadingPreviewController.java @@ -104,13 +104,11 @@ class TextReadingPreviewController extends BasePreferenceController implements final boolean isLayoutRtl = origConfig.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; final int[] previewSamples = getPreviewSampleLayouts(mContext); - final int[] previewContentDescriptions = getPreviewSampleContentDescriptions(mContext); final PreviewPagerAdapter pagerAdapter = new PreviewPagerAdapter(mContext, isLayoutRtl, previewSamples, createConfig(origConfig)); mPreviewPreference.setPreviewAdapter(pagerAdapter); mPreviewPreference.setCurrentItem( isLayoutRtl ? previewSamples.length - 1 : FRAME_INITIAL_INDEX); - mPreviewPreference.setContentDescription(previewContentDescriptions); final int initialPagerIndex = mLastFontProgress * mDisplaySizeData.getValues().size() + mLastDisplayProgress; @@ -190,20 +188,6 @@ class TextReadingPreviewController extends BasePreferenceController implements return previewSamples; } - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - static int[] getPreviewSampleContentDescriptions(Context context) { - TypedArray typedArray = context.getResources().obtainTypedArray( - R.array.config_text_reading_preview_content_descriptions); - int previewCount = typedArray.length(); - int[] previewContentDescriptions = new int[previewCount]; - for (int i = 0; i < previewCount; i++) { - previewContentDescriptions[i] = - typedArray.getResourceId(i, R.string.preview_pager_content_description); - } - typedArray.recycle(); - return previewContentDescriptions; - } - private int getPagerIndex() { final int displayDataSize = mDisplaySizeData.getValues().size(); final int fontSizeProgress = mFontSizePreference.getProgress(); diff --git a/src/com/android/settings/accessibility/TextReadingPreviewPreference.java b/src/com/android/settings/accessibility/TextReadingPreviewPreference.java index a4676baf58a..6d607364dea 100644 --- a/src/com/android/settings/accessibility/TextReadingPreviewPreference.java +++ b/src/com/android/settings/accessibility/TextReadingPreviewPreference.java @@ -43,10 +43,26 @@ public class TextReadingPreviewPreference extends Preference { private int mCurrentItem; private int mLastLayerIndex; private PreviewPagerAdapter mPreviewAdapter; - private int[] mContentDescriptions; private int mLayoutMinHorizontalPadding = 0; private int mBackgroundMinHorizontalPadding = 0; + private final ViewPager.OnPageChangeListener mPageChangeListener = + new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int i, float v, int i1) { + // Do nothing + } + + @Override + public void onPageSelected(int i) { + mCurrentItem = i; + } + + @Override + public void onPageScrollStateChanged(int i) { + // Do nothing + } + }; TextReadingPreviewPreference(Context context) { super(context); @@ -78,23 +94,7 @@ public class TextReadingPreviewPreference extends Preference { adjustPaddings(previewLayout, backgroundView); final ViewPager viewPager = (ViewPager) holder.findViewById(R.id.preview_pager); - viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { - @Override - public void onPageScrolled(int i, float v, int i1) { - // Do nothing - } - - @Override - public void onPageSelected(int i) { - mCurrentItem = i; - viewPager.setContentDescription(getContext().getString(mContentDescriptions[i])); - } - - @Override - public void onPageScrollStateChanged(int i) { - // Do nothing - } - }); + viewPager.addOnPageChangeListener(mPageChangeListener); final DotsPageIndicator pageIndicator = (DotsPageIndicator) holder.findViewById(R.id.page_indicator); updateAdapterIfNeeded(viewPager, pageIndicator, mPreviewAdapter); @@ -121,10 +121,6 @@ public class TextReadingPreviewPreference extends Preference { viewPager.setCurrentItem(getCurrentItem() + 1)); nextButton.setContentDescription(getContext().getString( R.string.preview_pager_next_button)); - - // Initialize the content description since the OnPageChangeListener#onPageSelected won't - // be called during setup. - viewPager.setContentDescription(getContext().getString(mContentDescriptions[0])); } @Override @@ -173,10 +169,6 @@ public class TextReadingPreviewPreference extends Preference { ); } - void setContentDescription(int[] stringIds) { - mContentDescriptions = stringIds; - } - void setPreviewAdapter(PreviewPagerAdapter previewAdapter) { if (previewAdapter != mPreviewAdapter) { mPreviewAdapter = previewAdapter; diff --git a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java index 81c869d5063..375952f725c 100644 --- a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java @@ -16,8 +16,6 @@ package com.android.settings.accessibility; -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.verify; @@ -78,15 +76,6 @@ public class TextReadingPreviewControllerTest { mDisplaySizePreference = new AccessibilitySeekBarPreference(mContext, /* attr= */ null); } - @Test - public void numberOfPreviewSamples_numberOfPreviewContentDescription_isEqual() { - int[] previewSamples = TextReadingPreviewController.getPreviewSampleLayouts(mContext); - int[] previewContentDescriptions = - TextReadingPreviewController.getPreviewSampleContentDescriptions(mContext); - - assertThat(previewSamples.length).isEqualTo(previewContentDescriptions.length); - } - @Test public void initPreviewerAdapter_verifyAction() { when(mPreferenceScreen.findPreference(PREVIEW_KEY)).thenReturn(mPreviewPreference); diff --git a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewPreferenceTest.java b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewPreferenceTest.java index 9cd8fa232c5..ac503bd9ebc 100644 --- a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewPreferenceTest.java @@ -49,48 +49,28 @@ import org.robolectric.RobolectricTestRunner; */ @RunWith(RobolectricTestRunner.class) public class TextReadingPreviewPreferenceTest { - private Context mContext; private TextReadingPreviewPreference mTextReadingPreviewPreference; private PreferenceViewHolder mHolder; private ViewPager mViewPager; private PreviewPagerAdapter mPreviewPagerAdapter; private int mPreviewSampleCount; - private int[] mPreviewContentDescriptions; @Before public void setUp() { - mContext = ApplicationProvider.getApplicationContext(); - mPreviewContentDescriptions = - TextReadingPreviewController.getPreviewSampleContentDescriptions(mContext); - final int[] previewSamples = TextReadingPreviewController.getPreviewSampleLayouts(mContext); + final Context context = ApplicationProvider.getApplicationContext(); + final int[] previewSamples = TextReadingPreviewController.getPreviewSampleLayouts(context); mPreviewSampleCount = previewSamples.length; final Configuration[] configurations = createConfigurations(mPreviewSampleCount); - mTextReadingPreviewPreference = new TextReadingPreviewPreference(mContext); + mTextReadingPreviewPreference = new TextReadingPreviewPreference(context); mPreviewPagerAdapter = - spy(new PreviewPagerAdapter(mContext, /* isLayoutRtl= */ false, + spy(new PreviewPagerAdapter(context, /* isLayoutRtl= */ false, previewSamples, configurations)); - final LayoutInflater inflater = LayoutInflater.from(mContext); + final LayoutInflater inflater = LayoutInflater.from(context); final View view = inflater.inflate(mTextReadingPreviewPreference.getLayoutResource(), - new LinearLayout(mContext), false); + new LinearLayout(context), false); mHolder = PreferenceViewHolder.createInstanceForTests(view); mViewPager = view.findViewById(R.id.preview_pager); - mTextReadingPreviewPreference.setContentDescription(mPreviewContentDescriptions); - } - - @Test - public void changePreviewPage_getExpectedContentDescription() { - mTextReadingPreviewPreference.setPreviewAdapter(mPreviewPagerAdapter); - mTextReadingPreviewPreference.onBindViewHolder(mHolder); - - // Verify the initial content description - assertThat(mViewPager.getContentDescription().toString()) - .isEqualTo(mContext.getString(mPreviewContentDescriptions[0])); - - // Change the preview page - mViewPager.setCurrentItem(1); - assertThat(mViewPager.getContentDescription().toString()) - .isEqualTo(mContext.getString(mPreviewContentDescriptions[1])); } @Test From 7ca28dd3d675343c137bc0403e3c9aae387a2a35 Mon Sep 17 00:00:00 2001 From: tom hsu Date: Mon, 17 Mar 2025 03:52:02 +0000 Subject: [PATCH 05/11] [Satellite] Move entitlment check to auto type only. Flag: EXEMPT bug fix Bug: b/401648126 Test: atest pass Change-Id: Ide6f8520b5e1e60f700586ef00970a4f55f5babe --- .../SatelliteSettingPreferenceController.java | 10 +++++----- .../SatelliteSettingsPreferenceControllerTest.java | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/com/android/settings/network/telephony/satellite/SatelliteSettingPreferenceController.java b/src/com/android/settings/network/telephony/satellite/SatelliteSettingPreferenceController.java index 18217fd5d62..dde35ebb9e7 100644 --- a/src/com/android/settings/network/telephony/satellite/SatelliteSettingPreferenceController.java +++ b/src/com/android/settings/network/telephony/satellite/SatelliteSettingPreferenceController.java @@ -186,17 +186,17 @@ public class SatelliteSettingPreferenceController extends return; } - if (!mCarrierConfigs.getBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL)) { - preference.setSummary(R.string.satellite_setting_summary_without_entitlement); - return; - } - if (isCarrierRoamingNtnConnectedTypeManual()) { preference.setSummary( mCarrierRoamingNtnModeCallback.isSatelliteSmsAvailable() ? R.string.satellite_setting_enabled_summary : R.string.satellite_setting_disabled_summary); } else { + if (!mCarrierConfigs.getBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL)) { + preference.setSummary(R.string.satellite_setting_summary_without_entitlement); + return; + } + try { Set restrictionReason = mSatelliteManager.getAttachRestrictionReasonsForCarrier(mSubId); diff --git a/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingsPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingsPreferenceControllerTest.java index c67f0aca6a1..c527d68401a 100644 --- a/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingsPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingsPreferenceControllerTest.java @@ -217,7 +217,10 @@ public class SatelliteSettingsPreferenceControllerTest { @Test @EnableFlags(com.android.settings.flags.Flags.FLAG_SATELLITE_OEM_SETTINGS_UX_MIGRATION) - public void summary_noEntitlement_showSummaryWithoutEntitlement() { + public void summary_noEntitlementAndTypeIsAuto_showSummaryWithoutEntitlement() { + mCarrierConfig.putInt( + CarrierConfigManager.KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, + CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC); mCarrierConfig.putBoolean( KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false); @@ -237,9 +240,6 @@ public class SatelliteSettingsPreferenceControllerTest { @Test @EnableFlags(com.android.settings.flags.Flags.FLAG_SATELLITE_OEM_SETTINGS_UX_MIGRATION) public void summary_smsAvailableForManualType_showSummaryWithAccount() { - mCarrierConfig.putBoolean( - KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, - true); mCarrierConfig.putInt( CarrierConfigManager.KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, CARRIER_ROAMING_NTN_CONNECT_MANUAL); From 68286922386efb7812b8a2c62357db3da3c9f48a Mon Sep 17 00:00:00 2001 From: tom hsu Date: Mon, 17 Mar 2025 04:05:31 +0000 Subject: [PATCH 06/11] Fix test failed due to wrong string. Flag: EXEMPT bug fix Fix: b/404076543 Test: atest pass Change-Id: Id1152aeed25cb2956b54a9071a0a76bc2a938b15 --- .../satellite/SatelliteSettingAboutContentControllerTest.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingAboutContentControllerTest.kt b/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingAboutContentControllerTest.kt index d070811c3b9..036cc3ff68b 100644 --- a/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingAboutContentControllerTest.kt +++ b/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingAboutContentControllerTest.kt @@ -63,11 +63,9 @@ class SatelliteSettingAboutContentControllerTest { controller.displayPreference(screen) assertThat(preference.title).isEqualTo( - context.getString( - R.string.description_about_satellite_setting, - TEST_SIM_OPERATOR_NAME + "You can send and receive text messages and use some apps by satellite with an eligible Test Carrier account" ) - ) + } private companion object { From c79e14a2fdfccada2fc1ac81eb908e5011e4247d Mon Sep 17 00:00:00 2001 From: Yiyi Shen Date: Mon, 17 Mar 2025 16:18:32 +0800 Subject: [PATCH 07/11] Move setScanMode to background thread to avoid ANR Test: atest Bug: 397951829 Flag: EXEMPT small fix Change-Id: I6450fc9cef1cfea3bb940e5d37a552df1f75dd23 --- .../bluetooth/AlwaysDiscoverable.java | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/com/android/settings/bluetooth/AlwaysDiscoverable.java b/src/com/android/settings/bluetooth/AlwaysDiscoverable.java index 2ac4a18c645..5ea99ae194e 100644 --- a/src/com/android/settings/bluetooth/AlwaysDiscoverable.java +++ b/src/com/android/settings/bluetooth/AlwaysDiscoverable.java @@ -24,6 +24,8 @@ import android.content.IntentFilter; import androidx.annotation.VisibleForTesting; +import com.android.settingslib.utils.ThreadUtils; + /** Helper class, intended to be used by an Activity, to keep the local Bluetooth adapter in * discoverable mode indefinitely. By default setting the scan mode to * BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE will time out after some time, but some @@ -55,10 +57,12 @@ public class AlwaysDiscoverable extends BroadcastReceiver { mContext.registerReceiver(this, mIntentFilter, Context.RECEIVER_EXPORTED_UNAUDITED); mStarted = true; - if (mBluetoothAdapter.getScanMode() - != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { - mBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); - } + ThreadUtils.postOnBackgroundThread(() -> { + if (mBluetoothAdapter.getScanMode() + != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { + mBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); + } + }); } public void stop() { @@ -67,7 +71,8 @@ public class AlwaysDiscoverable extends BroadcastReceiver { } mContext.unregisterReceiver(this); mStarted = false; - mBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE); + ThreadUtils.postOnBackgroundThread( + () -> mBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE)); } @Override @@ -76,9 +81,11 @@ public class AlwaysDiscoverable extends BroadcastReceiver { if (action != BluetoothAdapter.ACTION_SCAN_MODE_CHANGED) { return; } - if (mBluetoothAdapter.getScanMode() - != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { - mBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); - } + ThreadUtils.postOnBackgroundThread(() -> { + if (mBluetoothAdapter.getScanMode() + != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { + mBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); + } + }); } } From 006007446bcff2867ea0b6b4dc0f9866a09da9aa Mon Sep 17 00:00:00 2001 From: tom hsu Date: Mon, 17 Mar 2025 09:45:26 +0000 Subject: [PATCH 08/11] [Satellilte] Remove dynamic string of messaging and connectivity Flag: EXEMPT bug fix Bug: b/401648126 Test: Manual test. Change-Id: I3ec28cfa2a05b421f19918414af8e75c5c0b224e --- res/values/strings.xml | 16 ++++----- res/xml/mobile_network_settings.xml | 2 +- .../telephony/satellite/SatelliteSetting.java | 35 ++----------------- .../SatelliteSettingPreferenceController.java | 7 ---- 4 files changed, 10 insertions(+), 50 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 28e3ba64bc6..3cef0b81742 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -3695,10 +3695,6 @@ While docked - - - Satellite messaging - APNs @@ -12687,8 +12683,10 @@ Data usage charges may apply. Invalid Network Mode %1$d. Ignore. - - Satellite messaging + + + + Satellite connectivity Send and receive text messages by satellite. Included with your account. @@ -12731,14 +12729,14 @@ Data usage charges may apply. A satellite connection may be slower and is available only in some areas. Weather and certain structures may affect the connection. Calling by satellite isn\u2019t available. Emergency calls may still connect.\n\nIt may take some time for account changes to show in Settings. Contact %1$s for details. A satellite connection may be slower and is available only in some areas. Weather and certain structures may affect the connection. Calling by satellite isn\u2019t available. Emergency calls may still connect. Texting with emergency services may not be available in all areas.\n\nIt may take some time for account changes to show in Settings. Contact %1$s for details. - - More about %1$s + + More about satellite connectivity Can’t turn on %1$s To turn on %1$s, first end the satellite connection - Satellite connectivity + Satellite Satellite connectivity diff --git a/res/xml/mobile_network_settings.xml b/res/xml/mobile_network_settings.xml index a0da44051c6..12f3bebdb73 100644 --- a/res/xml/mobile_network_settings.xml +++ b/res/xml/mobile_network_settings.xml @@ -210,7 +210,7 @@ diff --git a/src/com/android/settings/network/telephony/satellite/SatelliteSetting.java b/src/com/android/settings/network/telephony/satellite/SatelliteSetting.java index 851038a7a45..0540912a9a9 100644 --- a/src/com/android/settings/network/telephony/satellite/SatelliteSetting.java +++ b/src/com/android/settings/network/telephony/satellite/SatelliteSetting.java @@ -133,7 +133,6 @@ public class SatelliteSetting extends RestrictedDashboardFragment { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); boolean isSatelliteEligible = isSatelliteEligible(); - updateTitle(); updateMobilePlan(isSatelliteEligible); updateHowItWorksContent(isSatelliteEligible); updateFooterContent(); @@ -149,10 +148,6 @@ public class SatelliteSetting extends RestrictedDashboardFragment { return R.xml.satellite_setting; } - private void updateTitle() { - findPreference("satellite_setting").setTitle(getSubjectString()); - } - private void updateMobilePlan(boolean isSatelliteEligible) { PreferenceCategory prefCategory = findPreference(PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN); if (prefCategory == null || !mConfigBundle.getBoolean( @@ -238,7 +233,7 @@ public class SatelliteSetting extends RestrictedDashboardFragment { final String[] link = new String[1]; link[0] = readSatelliteMoreInfoString(); - if (link[0] != null && !link[0].isEmpty()) { + if (true) { footerPreference.setLearnMoreAction(view -> { if (!link[0].isEmpty()) { Intent helpIntent = HelpUtils.getHelpIntent(mActivity, link[0], @@ -250,7 +245,7 @@ public class SatelliteSetting extends RestrictedDashboardFragment { }); footerPreference.setLearnMoreText( - getString(R.string.more_about_satellite_messaging, getDescriptionString())); + getString(R.string.more_about_satellite_messaging)); } } } @@ -304,32 +299,6 @@ public class SatelliteSetting extends RestrictedDashboardFragment { return mConfigBundle.getBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, false); } - // This is for a word which first letter is uppercase. e.g. Satellite messaging. - private String getSubjectString() { - int result; - if (com.android.settings.flags.Flags.satelliteOemSettingsUxMigration()) { - result = mIsServiceDataType - ? R.string.title_satellite_setting_connectivity - : R.string.satellite_setting_title; - } else { - result = R.string.satellite_setting_title; - } - return getString(result); - } - - // This is for a word without uppercase letter. e.g. satellite messaging. - private String getDescriptionString() { - int result; - if (com.android.settings.flags.Flags.satelliteOemSettingsUxMigration()) { - result = mIsServiceDataType - ? R.string.description_satellite_setting_connectivity - : R.string.description_satellite_setting_messaging; - } else { - result = R.string.satellite_setting_title; - } - return getString(result); - } - private static void loge(String message) { Log.e(TAG, message); } diff --git a/src/com/android/settings/network/telephony/satellite/SatelliteSettingPreferenceController.java b/src/com/android/settings/network/telephony/satellite/SatelliteSettingPreferenceController.java index 18217fd5d62..1ecbf47deef 100644 --- a/src/com/android/settings/network/telephony/satellite/SatelliteSettingPreferenceController.java +++ b/src/com/android/settings/network/telephony/satellite/SatelliteSettingPreferenceController.java @@ -145,7 +145,6 @@ public class SatelliteSettingPreferenceController extends public void updateState(@Nullable Preference preference) { super.updateState(preference); if (preference != null && preference.getKey().equals(getPreferenceKey())) { - updateTitle(preference); updateSummary(preference); } } @@ -170,12 +169,6 @@ public class SatelliteSettingPreferenceController extends return false; } - private void updateTitle(Preference preference) { - preference.setTitle(mCarrierRoamingNtnModeCallback.isSatelliteServiceDataType() - ? R.string.title_satellite_setting_connectivity - : R.string.satellite_setting_title); - } - private void updateSummary(Preference preference) { if (preference == null) { logd("updateSummary - no Preference"); From 91dab36efdb6f7a2ab1948cfbbcc1de66b1a50d8 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Mon, 17 Mar 2025 17:58:50 +0800 Subject: [PATCH 09/11] Change storage_summary string id to fix crash ag/32322018 reverts storage_summary string for default locale but other languages might be still using stale translations and cause MissingFormatArgumentException crash. As a quick fix, change the string id to make it as a new string. The side-effect is that storage summary is not localized for a while. Fix: 404116634 Flag: EXEMPT bugfix Test: Verified with zh_TW locale Change-Id: I5d7d6be2c1eb524622f1013120cb44403b1f6f29 --- res/values/strings.xml | 2 +- .../deviceinfo/TopLevelStoragePreferenceController.java | 2 +- .../homepage/contextualcards/slices/LowStorageSlice.java | 2 +- .../deviceinfo/TopLevelStoragePreferenceControllerTest.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 28e3ba64bc6..2cfbf482e3c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10914,7 +10914,7 @@ Data usage charges may apply. %1$d apps installed - %1$s used - %2$s free + %1$s used - %2$s free Dark theme, font size, brightness diff --git a/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceController.java b/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceController.java index 1955f36059a..05e4f25ee78 100644 --- a/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceController.java +++ b/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceController.java @@ -93,7 +93,7 @@ public class TopLevelStoragePreferenceController extends BasePreferenceControlle private String getSummary(long usedBytes, long totalBytes) { NumberFormat percentageFormat = NumberFormat.getPercentInstance(); - return mContext.getString(R.string.storage_summary, + return mContext.getString(R.string.storage_toplevel_summary, totalBytes == 0L ? "0" : percentageFormat.format(((double) usedBytes) / totalBytes), Formatter.formatFileSize(mContext, totalBytes - usedBytes)); } diff --git a/src/com/android/settings/homepage/contextualcards/slices/LowStorageSlice.java b/src/com/android/settings/homepage/contextualcards/slices/LowStorageSlice.java index 22e34317702..2a731bf50b7 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/LowStorageSlice.java +++ b/src/com/android/settings/homepage/contextualcards/slices/LowStorageSlice.java @@ -73,7 +73,7 @@ public class LowStorageSlice implements CustomSliceable { if (usedPercentage < LOW_STORAGE_THRESHOLD) { // For clients that ignore error checking, a generic storage slice will be given. final CharSequence titleStorage = mContext.getText(R.string.storage_settings); - final String summaryStorage = mContext.getString(R.string.storage_summary, + final String summaryStorage = mContext.getString(R.string.storage_toplevel_summary, percentageString, freeSizeString); return listBuilder diff --git a/tests/unit/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceControllerTest.java b/tests/unit/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceControllerTest.java index 6318c9c6914..c5c69af998f 100644 --- a/tests/unit/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceControllerTest.java @@ -103,6 +103,6 @@ public class TopLevelStoragePreferenceControllerTest { // the background thread. TimeUnit.SECONDS.sleep(5); assertThat(preference.getSummary()).isEqualTo(ResourcesUtils.getResourcesString( - mContext, "storage_summary", percentage, freeSpace)); + mContext, "storage_toplevel_summary", percentage, freeSpace)); } } From f0efc2e36336d246fcb842f891907f57e33d140f Mon Sep 17 00:00:00 2001 From: tom hsu Date: Mon, 17 Mar 2025 09:27:21 +0000 Subject: [PATCH 10/11] [Satellite] Refactor code to controller base. - Your mobile plan Flag: EXEMPT refactor Bug: b/403149290 Test: atest pass Change-Id: Ia21f3d8b301401799263a1a2b43d82e9a46729a5 --- res/xml/satellite_setting.xml | 3 +- .../telephony/satellite/SatelliteSetting.java | 82 +------ ...SatelliteSettingAccountInfoController.java | 178 +++++++++++++++ ...lliteSettingAccountInfoControllerTest.java | 210 ++++++++++++++++++ 4 files changed, 397 insertions(+), 76 deletions(-) create mode 100644 src/com/android/settings/network/telephony/satellite/SatelliteSettingAccountInfoController.java create mode 100644 tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingAccountInfoControllerTest.java diff --git a/res/xml/satellite_setting.xml b/res/xml/satellite_setting.xml index 74bee71c31a..06509700cf7 100644 --- a/res/xml/satellite_setting.xml +++ b/res/xml/satellite_setting.xml @@ -32,7 +32,8 @@ + android:title="@string/category_title_your_satellite_plan" + settings:controller="com.android.settings.network.telephony.satellite.SatelliteSettingAccountInfoController"> { - String url = readSatelliteMoreInfoString(); - if (!url.isEmpty()) { - Uri uri = Uri.parse(url); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - startActivity(intent); - } - return true; - }); - icon = getResources().getDrawable(R.drawable.ic_block_24px, null); - } - icon.setTintList(Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary)); - messagingPreference.setIcon(icon); - } - private void updateHowItWorksContent(boolean isSatelliteEligible) { /* Composes "How it works" section, which guides how users can use satellite messaging, when satellite messaging is included in user's mobile plan, or it'll will be grey out. */ @@ -233,7 +165,7 @@ public class SatelliteSetting extends RestrictedDashboardFragment { final String[] link = new String[1]; link[0] = readSatelliteMoreInfoString(); - if (true) { + if (link[0] != null && !link[0].isEmpty()) { footerPreference.setLearnMoreAction(view -> { if (!link[0].isEmpty()) { Intent helpIntent = HelpUtils.getHelpIntent(mActivity, link[0], diff --git a/src/com/android/settings/network/telephony/satellite/SatelliteSettingAccountInfoController.java b/src/com/android/settings/network/telephony/satellite/SatelliteSettingAccountInfoController.java new file mode 100644 index 00000000000..f688a92305e --- /dev/null +++ b/src/com/android/settings/network/telephony/satellite/SatelliteSettingAccountInfoController.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2025 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.network.telephony.satellite; + +import static android.telephony.CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_MANUAL; +import static android.telephony.CarrierConfigManager.KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT; +import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL; +import static android.telephony.CarrierConfigManager.KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.PersistableBundle; +import android.telephony.TelephonyManager; +import android.telephony.satellite.SatelliteManager; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.style.StyleSpan; +import android.text.style.UnderlineSpan; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.network.telephony.TelephonyBasePreferenceController; +import com.android.settingslib.Utils; + +import java.util.Set; + +/** A controller to control content of "Your mobile plan". */ +public class SatelliteSettingAccountInfoController extends TelephonyBasePreferenceController { + private static final String TAG = "SatelliteSettingAccountInfoController"; + @VisibleForTesting + static final String PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN = + "key_category_your_satellite_plan"; + @VisibleForTesting + static final String PREF_KEY_YOUR_SATELLITE_PLAN = "key_your_satellite_plan"; + @VisibleForTesting + static final String PREF_KEY_YOUR_SATELLITE_DATA_PLAN = "key_your_satellite_data_plan"; + + private PreferenceScreen mScreen; + private String mSimOperatorName; + private boolean mIsSmsAvailable; + private boolean mIsDataAvailable; + private boolean mIsSatelliteEligible; + private PersistableBundle mConfigBundle = new PersistableBundle(); + + public SatelliteSettingAccountInfoController(@NonNull Context context, + @NonNull String preferenceKey) { + super(context, preferenceKey); + } + + /** Initialize the UI settings. */ + public void init(int subId, @NonNull PersistableBundle configBundle, boolean isSmsAvailable, + boolean isDataAvailable) { + mSubId = subId; + mConfigBundle = configBundle; + mSimOperatorName = mContext.getSystemService(TelephonyManager.class).getSimOperatorName( + mSubId); + mIsSmsAvailable = isSmsAvailable; + mIsDataAvailable = isDataAvailable; + mIsSatelliteEligible = isSatelliteEligible(); + } + + @Override + public void displayPreference(@NonNull PreferenceScreen screen) { + mScreen = screen; + super.displayPreference(screen); + PreferenceCategory prefCategory = screen.findPreference( + PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN); + // Your mobile plan + prefCategory.setTitle(mContext.getString(R.string.category_title_your_satellite_plan, + mSimOperatorName)); + + if (mIsSatelliteEligible) { + handleEligibleUI(); + return; + } + handleIneligibleUI(); + } + + @Override + public int getAvailabilityStatus(int subId) { + return mConfigBundle.getBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL) + ? AVAILABLE + : CONDITIONALLY_UNAVAILABLE; + } + + private void handleEligibleUI() { + Preference messagingPreference = mScreen.findPreference(PREF_KEY_YOUR_SATELLITE_PLAN); + Drawable icon = mContext.getDrawable(R.drawable.ic_check_circle_24px); + /* In case satellite is allowed by carrier's entitlement server, the page will show + the check icon with guidance that satellite is included in user's mobile plan */ + messagingPreference.setTitle(R.string.title_have_satellite_plan); + if (com.android.settings.flags.Flags.satelliteOemSettingsUxMigration()) { + if (mIsDataAvailable) { + Preference connectivityPreference = mScreen.findPreference( + PREF_KEY_YOUR_SATELLITE_DATA_PLAN); + connectivityPreference.setTitle(R.string.title_have_satellite_data_plan); + connectivityPreference.setIcon(icon); + connectivityPreference.setVisible(true); + } + } + icon.setTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary)); + messagingPreference.setIcon(icon); + } + + private void handleIneligibleUI() { + Preference messagingPreference = mScreen.findPreference(PREF_KEY_YOUR_SATELLITE_PLAN); + /* Or, it will show the blocked icon with the guidance that satellite is not included + in user's mobile plan */ + messagingPreference.setTitle(R.string.title_no_satellite_plan); + String url = mConfigBundle.getString(KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING, ""); + if (!url.isEmpty()) { + /* And, the link url provides more information via web page will be shown */ + SpannableString spannable = new SpannableString( + mContext.getString(R.string.summary_add_satellite_setting)); + spannable.setSpan(new UnderlineSpan(), 0, spannable.length(), + Spanned.SPAN_INCLUSIVE_INCLUSIVE); + spannable.setSpan(new StyleSpan(Typeface.BOLD), 0, spannable.length(), + Spanned.SPAN_INCLUSIVE_INCLUSIVE); + messagingPreference.setSummary(spannable); + /* The link will lead users to a guide page */ + messagingPreference.setOnPreferenceClickListener(pref -> { + Uri uri = Uri.parse(url); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + mContext.startActivity(intent); + return true; + }); + } + + Drawable icon = mContext.getDrawable(R.drawable.ic_block_24px); + icon.setTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary)); + messagingPreference.setIcon(icon); + } + + @VisibleForTesting + protected boolean isSatelliteEligible() { + if (mConfigBundle.getInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT) + == CARRIER_ROAMING_NTN_CONNECT_MANUAL) { + return mIsSmsAvailable; + } + SatelliteManager satelliteManager = mContext.getSystemService(SatelliteManager.class); + if (satelliteManager == null) { + Log.d(TAG, "SatelliteManager is null."); + return false; + } + try { + Set restrictionReason = + satelliteManager.getAttachRestrictionReasonsForCarrier(mSubId); + return !restrictionReason.contains( + SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT); + } catch (SecurityException | IllegalStateException | IllegalArgumentException ex) { + Log.d(TAG, "Error to getAttachRestrictionReasonsForCarrier : " + ex.toString()); + return false; + } + } +} diff --git a/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingAccountInfoControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingAccountInfoControllerTest.java new file mode 100644 index 00000000000..331a74c8b5c --- /dev/null +++ b/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingAccountInfoControllerTest.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2025 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.network.telephony.satellite; + +import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; +import static com.android.settings.network.telephony.satellite.SatelliteSettingAccountInfoController.PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN; +import static com.android.settings.network.telephony.satellite.SatelliteSettingAccountInfoController.PREF_KEY_YOUR_SATELLITE_DATA_PLAN; +import static com.android.settings.network.telephony.satellite.SatelliteSettingAccountInfoController.PREF_KEY_YOUR_SATELLITE_PLAN; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Looper; +import android.os.PersistableBundle; +import android.telephony.TelephonyManager; +import android.telephony.satellite.SatelliteManager; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settings.testutils.ResourcesUtils; + +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; + +@RunWith(AndroidJUnit4.class) +public class SatelliteSettingAccountInfoControllerTest { + private static final int TEST_SUB_ID = 5; + private static final String TEST_OPERATOR_NAME = "test_operator_name"; + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Mock + private TelephonyManager mTelephonyManager; + + private Context mContext; + private SatelliteSettingAccountInfoController mController; + private final PersistableBundle mPersistableBundle = new PersistableBundle(); + + @Before + public void setUp() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mContext = spy(ApplicationProvider.getApplicationContext()); + mController = new SatelliteSettingAccountInfoController(mContext, + PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN); + when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); + when(mTelephonyManager.getSimOperatorName(TEST_SUB_ID)).thenReturn(TEST_OPERATOR_NAME); + } + + @Test + public void getAvailabilityStatus_entitlementNotSupport_returnConditionalUnavailable() { + when(mContext.getSystemService(SatelliteManager.class)).thenReturn(null); + mController.init(TEST_SUB_ID, mPersistableBundle, false, false); + + int result = mController.getAvailabilityStatus(TEST_SUB_ID); + + assertThat(result).isEqualTo(CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void getAvailabilityStatus_entitlementIsSupported_returnConditionalUnavailable() { + mPersistableBundle.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true); + mController.init(TEST_SUB_ID, mPersistableBundle, false, false); + + int result = mController.getAvailabilityStatus(TEST_SUB_ID); + + assertThat(result).isEqualTo(AVAILABLE); + } + + @Test + public void displayPreference_showCategoryTitle_correctOperatorName() { + mPersistableBundle.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true); + when(mContext.getSystemService(SatelliteManager.class)).thenReturn(null); + mController.init(TEST_SUB_ID, mPersistableBundle, false, false); + + PreferenceScreen screen = new PreferenceManager(mContext).createPreferenceScreen(mContext); + PreferenceCategory preferenceCategory = new PreferenceCategory(mContext); + preferenceCategory.setKey(PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN); + Preference preference = new Preference(mContext); + preference.setKey(PREF_KEY_YOUR_SATELLITE_PLAN); + screen.addPreference(preferenceCategory); + screen.addPreference(preference); + + mController.displayPreference(screen); + + assertThat(preferenceCategory.getTitle().toString()).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "category_title_your_satellite_plan", + TEST_OPERATOR_NAME)); + } + + @Test + public void displayPreference_showEligibleUiButDataUnavailable_showSmsEligibleAccountState() { + mPersistableBundle.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true); + when(mContext.getSystemService(SatelliteManager.class)).thenReturn(null); + mController = new SatelliteSettingAccountInfoController(mContext, + PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN) { + @Override + protected boolean isSatelliteEligible() { + return true; + } + }; + mController.init(TEST_SUB_ID, mPersistableBundle, true, false); + PreferenceScreen screen = new PreferenceManager(mContext).createPreferenceScreen(mContext); + PreferenceCategory preferenceCategory = new PreferenceCategory(mContext); + preferenceCategory.setKey(PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN); + Preference preference = new Preference(mContext); + preference.setKey(PREF_KEY_YOUR_SATELLITE_PLAN); + Preference preferenceData = new Preference(mContext); + preferenceData.setKey(PREF_KEY_YOUR_SATELLITE_DATA_PLAN); + screen.addPreference(preferenceCategory); + screen.addPreference(preference); + screen.addPreference(preferenceData); + + mController.displayPreference(screen); + + assertThat(preference.getTitle().toString()).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "title_have_satellite_plan")); + assertThat(preferenceData.getTitle()).isEqualTo(null); + } + + @Test + public void + displayPreference_showEligibleUiAndDataAvailable_showSmsAndDataEligibleAccountState() { + mPersistableBundle.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true); + when(mContext.getSystemService(SatelliteManager.class)).thenReturn(null); + mController = new SatelliteSettingAccountInfoController(mContext, + PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN) { + @Override + protected boolean isSatelliteEligible() { + return true; + } + }; + mController.init(TEST_SUB_ID, mPersistableBundle, true, true); + PreferenceScreen screen = new PreferenceManager(mContext).createPreferenceScreen(mContext); + PreferenceCategory preferenceCategory = new PreferenceCategory(mContext); + preferenceCategory.setKey(PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN); + Preference preference = new Preference(mContext); + preference.setKey(PREF_KEY_YOUR_SATELLITE_PLAN); + Preference preferenceData = new Preference(mContext); + preferenceData.setKey(PREF_KEY_YOUR_SATELLITE_DATA_PLAN); + screen.addPreference(preferenceCategory); + screen.addPreference(preference); + screen.addPreference(preferenceData); + + mController.displayPreference(screen); + + assertThat(preference.getTitle().toString()).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "title_have_satellite_plan")); + assertThat(preferenceData.getTitle().toString()).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "title_have_satellite_data_plan")); + } + + @Test + public void displayPreference_showIneligibleUi_showSmsAccountState() { + mPersistableBundle.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true); + when(mContext.getSystemService(SatelliteManager.class)).thenReturn(null); + mController = new SatelliteSettingAccountInfoController(mContext, + PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN) { + @Override + protected boolean isSatelliteEligible() { + return false; + } + }; + mController.init(TEST_SUB_ID, mPersistableBundle, false, false); + PreferenceScreen screen = new PreferenceManager(mContext).createPreferenceScreen(mContext); + PreferenceCategory preferenceCategory = new PreferenceCategory(mContext); + preferenceCategory.setKey(PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN); + Preference preference = new Preference(mContext); + preference.setKey(PREF_KEY_YOUR_SATELLITE_PLAN); + screen.addPreference(preferenceCategory); + screen.addPreference(preference); + + mController.displayPreference(screen); + + assertThat(preference.getTitle().toString()).isEqualTo( + ResourcesUtils.getResourcesString(mContext, "title_no_satellite_plan")); + } +} From 9757bd2d5fa4c6a6e2f0ac5134e23a2604c24ce7 Mon Sep 17 00:00:00 2001 From: tom hsu Date: Mon, 17 Mar 2025 14:32:21 +0000 Subject: [PATCH 11/11] [Satellite] Remove useless unitest Flag: EXEMPT bug fix Bug: b/387596751 Test: atest pass Change-Id: I26c676b36ebfd6727b69adadb753e3c2697c8780 --- ...lliteSettingsPreferenceControllerTest.java | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingsPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingsPreferenceControllerTest.java index c527d68401a..7cb85a580e4 100644 --- a/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingsPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/network/telephony/satellite/SatelliteSettingsPreferenceControllerTest.java @@ -19,7 +19,6 @@ package com.android.settings.network.telephony.satellite; import static android.telephony.CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC; import static android.telephony.CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_MANUAL; import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL; -import static android.telephony.NetworkRegistrationInfo.SERVICE_TYPE_DATA; import static android.telephony.NetworkRegistrationInfo.SERVICE_TYPE_SMS; import static com.android.settings.core.BasePreferenceController.AVAILABLE; @@ -181,40 +180,6 @@ public class SatelliteSettingsPreferenceControllerTest { verify(mTelephonyManager).unregisterTelephonyCallback(any()); } - @Test - @EnableFlags(com.android.settings.flags.Flags.FLAG_SATELLITE_OEM_SETTINGS_UX_MIGRATION) - public void title_hasServiceDataType_showDataUi() { - mController.initialize(TEST_SUB_ID); - PreferenceManager preferenceManager = new PreferenceManager(mContext); - PreferenceScreen preferenceScreen = preferenceManager.createPreferenceScreen(mContext); - Preference preference = new Preference(mContext); - preference.setKey(KEY); - preference.setTitle("test title"); - preferenceScreen.addPreference(preference); - mController.displayPreference(preferenceScreen); - mController.mCarrierRoamingNtnModeCallback.onCarrierRoamingNtnAvailableServicesChanged( - new int[]{SERVICE_TYPE_SMS, SERVICE_TYPE_DATA}); - - assertThat(preference.getTitle()).isEqualTo("Satellite connectivity"); - } - - @Test - @EnableFlags(com.android.settings.flags.Flags.FLAG_SATELLITE_OEM_SETTINGS_UX_MIGRATION) - public void title_onlyHasServiceSmsType_showSmsUi() { - mController.initialize(TEST_SUB_ID); - PreferenceManager preferenceManager = new PreferenceManager(mContext); - PreferenceScreen preferenceScreen = preferenceManager.createPreferenceScreen(mContext); - Preference preference = new Preference(mContext); - preference.setKey(KEY); - preference.setTitle("test title"); - preferenceScreen.addPreference(preference); - mController.displayPreference(preferenceScreen); - mController.mCarrierRoamingNtnModeCallback.onCarrierRoamingNtnAvailableServicesChanged( - new int[]{SERVICE_TYPE_SMS}); - - assertThat(preference.getTitle()).isEqualTo("Satellite messaging"); - } - @Test @EnableFlags(com.android.settings.flags.Flags.FLAG_SATELLITE_OEM_SETTINGS_UX_MIGRATION) public void summary_noEntitlementAndTypeIsAuto_showSummaryWithoutEntitlement() {