From 1189e24e47571eae86634aeaa7dc60b8fe7f4820 Mon Sep 17 00:00:00 2001 From: Adam Bookatz Date: Mon, 22 Jul 2024 17:03:12 -0700 Subject: [PATCH 1/9] startActivityForResult with new Intent Rather than use the raw Intent, we make a copy of it. See bug. Bug: 330722900 Flag: EXEMPT bugfix Test: manual Test: atest com.android.settings.users.UserSettingsTest com.android.settings.users.UserDetailsSettingsTest Change-Id: Id74e4b7ae261f2916eedaef04a679f83409a4b67 --- src/com/android/settings/users/AppRestrictionsFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/users/AppRestrictionsFragment.java b/src/com/android/settings/users/AppRestrictionsFragment.java index 1532448718c..c42e2f57b1d 100644 --- a/src/com/android/settings/users/AppRestrictionsFragment.java +++ b/src/com/android/settings/users/AppRestrictionsFragment.java @@ -651,7 +651,7 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen int requestCode = generateCustomActivityRequestCode( RestrictionsResultReceiver.this.preference); AppRestrictionsFragment.this.startActivityForResult( - restrictionsIntent, requestCode); + new Intent(restrictionsIntent), requestCode); } } } From 184bca9c16ba591dc552696d553c051ccdd37c39 Mon Sep 17 00:00:00 2001 From: Kai Zhou Date: Sun, 28 Jul 2024 12:04:41 +0000 Subject: [PATCH 2/9] Add unit test in WifiDppConfiguratorActivity * Bug: b/349252886 * Settings Intake: b/329012096 Test: atest tests/unit/src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivityTest.java Flag: com.android.settings.flags.enable_wifi_sharing_runtime_fragment Change-Id: I951ed57acd40505c1ccecdb8420d197f7edd42f7 --- .../dpp/WifiDppConfiguratorActivityTest.java | 71 ++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/tests/unit/src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivityTest.java b/tests/unit/src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivityTest.java index 4d723dc1846..34c86024739 100644 --- a/tests/unit/src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivityTest.java +++ b/tests/unit/src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivityTest.java @@ -20,15 +20,33 @@ import static android.os.UserManager.DISALLOW_ADD_WIFI_CONFIG; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; import android.content.Context; +import android.content.Intent; import android.os.UserManager; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; import androidx.test.annotation.UiThreadTest; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.android.settings.flags.Flags; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.wifi.factory.WifiFeatureProvider; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -44,18 +62,36 @@ public class WifiDppConfiguratorActivityTest { @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Spy private final Context mContext = ApplicationProvider.getApplicationContext(); @Mock private UserManager mUserManager; + @Mock + private FragmentManager mFragmentManager; + // Mock, created by FakeFeatureFactory + private WifiFeatureProvider mWifiFeatureProviderMock; + + @Spy private WifiDppConfiguratorActivity mActivity; @Before public void setUp() { when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); - mActivity = new WifiDppConfiguratorActivity(); + mActivity.mFragmentManager = mFragmentManager; + doReturn(mContext).when(mActivity).getApplicationContext(); + + FragmentTransaction mockTransaction = mock(FragmentTransaction.class); + when(mFragmentManager.beginTransaction()).thenReturn(mockTransaction); + when(mockTransaction.replace(anyInt(), any(Fragment.class), anyString())) + .thenReturn(mockTransaction); + + FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest(); + mWifiFeatureProviderMock = featureFactory.mWifiFeatureProvider; } @Test @@ -71,4 +107,37 @@ public class WifiDppConfiguratorActivityTest { assertThat(mActivity.isAddWifiConfigAllowed(mContext)).isFalse(); } + + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WIFI_SHARING_RUNTIME_FRAGMENT) + public void showQrCodeGeneratorFragment_shouldUseFeatureFactory() { + when(mUserManager.isGuestUser()).thenReturn(false); + when(mWifiFeatureProviderMock.getWifiDppQrCodeGeneratorFragment()) + .thenReturn(new WifiDppQrCodeGeneratorFragment()); + + mActivity.handleIntent(createQrCodeGeneratorIntent()); + + verify(mWifiFeatureProviderMock).getWifiDppQrCodeGeneratorFragment(); + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_WIFI_SHARING_RUNTIME_FRAGMENT) + public void showQrCodeGeneratorFragment_shouldNotUseFeatureFactory() { + when(mUserManager.isGuestUser()).thenReturn(false); + + mActivity.handleIntent(createQrCodeGeneratorIntent()); + + verify(mWifiFeatureProviderMock, never()) + .getWifiDppQrCodeGeneratorFragment(); + } + + private static Intent createQrCodeGeneratorIntent() { + Intent intent = new Intent( + WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_GENERATOR); + intent.putExtra(WifiDppUtils.EXTRA_WIFI_SSID, "GoogleGuest"); + intent.putExtra(WifiDppUtils.EXTRA_WIFI_SECURITY, "WPA"); + intent.putExtra(WifiDppUtils.EXTRA_WIFI_PRE_SHARED_KEY, "\\012345678,"); + return intent; + } } From e7cc2791f6fba4a65c503a1fabbb699735b6e12b Mon Sep 17 00:00:00 2001 From: tomhsu Date: Wed, 7 Aug 2024 07:18:08 +0000 Subject: [PATCH 3/9] DISALLOW_CONFIG_MOBILE_NETWORKS for Mobile Network - Avoid intent start MobileNetworkSettings page - Avoid Mobile network preference show on screen Flag: EXEMPT bug fix Fix: 289232540 Test: Manual test. see b/289232540#28 Test: atest passed Change-Id: I25b75673fbc0758dc06ca15f890e5dee0ea1367f --- .../network/SubscriptionsPreferenceController.java | 2 ++ .../settings/network/telephony/MobileNetworkSettings.java | 5 +++++ .../android/settings/widget/MutableGearPreference.java | 8 +++++--- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/network/SubscriptionsPreferenceController.java b/src/com/android/settings/network/SubscriptionsPreferenceController.java index d9a64646aef..b4ecc62c315 100644 --- a/src/com/android/settings/network/SubscriptionsPreferenceController.java +++ b/src/com/android/settings/network/SubscriptionsPreferenceController.java @@ -241,6 +241,8 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl if (mSubsGearPref == null) { mPreferenceGroup.removeAll(); mSubsGearPref = new MutableGearPreference(mContext, null); + mSubsGearPref + .checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS); mSubsGearPref.setOnPreferenceClickListener(preference -> { connectCarrierNetwork(); return true; diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java index d70ef25dd3a..699f09ee0a5 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java +++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java @@ -202,6 +202,11 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme @Override public void onAttach(Context context) { super.onAttach(context); + if (isUiRestricted()) { + Log.d(LOG_TAG, "Mobile network page is disallowed."); + finish(); + return; + } if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { Log.d(LOG_TAG, "Invalid subId, get the default subscription to show."); diff --git a/src/com/android/settings/widget/MutableGearPreference.java b/src/com/android/settings/widget/MutableGearPreference.java index b0804ebc2db..73491f02236 100644 --- a/src/com/android/settings/widget/MutableGearPreference.java +++ b/src/com/android/settings/widget/MutableGearPreference.java @@ -41,11 +41,13 @@ public class MutableGearPreference extends GearPreference { @Override public void setGearEnabled(boolean enabled) { + boolean state = false; if (mGear != null) { - mGear.setEnabled(enabled); - mGear.setImageAlpha(enabled ? VALUE_ENABLED_ALPHA : mDisabledAlphaValue); + state = enabled && !(isDisabledByAdmin() || isDisabledByEcm()); + mGear.setEnabled(state); + mGear.setImageAlpha(state ? VALUE_ENABLED_ALPHA : mDisabledAlphaValue); } - mGearState = enabled; + mGearState = state; } @Override From 5df87faad52483339c4812e46691e1067d619680 Mon Sep 17 00:00:00 2001 From: tomhsu Date: Thu, 8 Aug 2024 12:53:49 +0000 Subject: [PATCH 4/9] Add a warning dialog to notify user that call may end by operation. Flag: EXEMPT bugfix Fix: 299061626 Test: atest passed Test: Manual test passed Change-Id: I09e0186da45b0ccd00ccf0d86d6448a13b363298 --- .../system/reset/ResetNetworkConfirm.kt | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/com/android/settings/system/reset/ResetNetworkConfirm.kt b/src/com/android/settings/system/reset/ResetNetworkConfirm.kt index 34b9909e55e..e0403569872 100644 --- a/src/com/android/settings/system/reset/ResetNetworkConfirm.kt +++ b/src/com/android/settings/system/reset/ResetNetworkConfirm.kt @@ -18,6 +18,7 @@ package com.android.settings.system.reset import android.app.ProgressDialog import android.app.settings.SettingsEnums +import android.content.DialogInterface import android.os.Bundle import android.os.Looper import android.telephony.SubscriptionManager @@ -56,7 +57,8 @@ import kotlinx.coroutines.withContext * This is the confirmation screen. */ class ResetNetworkConfirm : InstrumentedFragment() { - @VisibleForTesting lateinit var resetNetworkRequest: ResetNetworkRequest + @VisibleForTesting + lateinit var resetNetworkRequest: ResetNetworkRequest private var progressDialog: ProgressDialog? = null private var alertDialog: AlertDialog? = null private var resetStarted = false @@ -87,10 +89,7 @@ class ResetNetworkConfirm : InstrumentedFragment() { /** Configure the UI for the final confirmation interaction */ private fun View.establishFinalConfirmationState() { requireViewById(R.id.execute_reset_network).setOnClickListener { - if (!Utils.isMonkeyRunning() && !resetStarted) { - resetStarted = true - viewLifecycleOwner.lifecycleScope.launch { onResetClicked() } - } + showResetInternetDialog(); } } @@ -118,10 +117,10 @@ class ResetNetworkConfirm : InstrumentedFragment() { private fun invalidSubIdFlow(): Flow { val subIdsInRequest = listOf( - resetNetworkRequest.resetTelephonyAndNetworkPolicyManager, - resetNetworkRequest.resetApnSubId, - resetNetworkRequest.resetImsSubId, - ) + resetNetworkRequest.resetTelephonyAndNetworkPolicyManager, + resetNetworkRequest.resetApnSubId, + resetNetworkRequest.resetImsSubId, + ) .distinct() .filter(SubscriptionManager::isUsableSubscriptionId) @@ -162,6 +161,24 @@ class ResetNetworkConfirm : InstrumentedFragment() { } } + private fun showResetInternetDialog() { + val builder = AlertDialog.Builder(requireContext()) + val resetInternetClickListener = + DialogInterface.OnClickListener { dialog, which -> + if (!Utils.isMonkeyRunning() && !resetStarted) { + resetStarted = true + viewLifecycleOwner.lifecycleScope.launch { onResetClicked() } + } + } + + builder.setTitle(R.string.reset_your_internet_title) + .setMessage(R.string.reset_internet_text) + .setPositiveButton(R.string.tts_reset, resetInternetClickListener) + .setNegativeButton(android.R.string.cancel, null) + .create() + .show() + } + /** * Do all reset task. * @@ -173,7 +190,8 @@ class ResetNetworkConfirm : InstrumentedFragment() { withContext(Dispatchers.Default) { val builder = resetNetworkRequest.toResetNetworkOperationBuilder( - requireContext(), Looper.getMainLooper()) + requireContext(), Looper.getMainLooper() + ) resetNetworkRequest.resetEsimPackageName?.let { resetEsimPackageName -> builder.resetEsim(resetEsimPackageName) builder.resetEsimResultCallback { resetEsimSuccess = it } @@ -199,8 +217,8 @@ class ResetNetworkConfirm : InstrumentedFragment() { } else { Toast.makeText(activity, R.string.reset_network_complete_toast, Toast.LENGTH_SHORT) .show() + activity.finish() } - activity.finish() } override fun onDestroy() { From 675b817c49d57d9ec2980d8a359c3827f328ae5c Mon Sep 17 00:00:00 2001 From: Weng Su Date: Fri, 9 Aug 2024 21:50:06 +0800 Subject: [PATCH 5/9] Hide new Hotspot preferences if feature is disabled - Hide new "Security" preference - Hide new "Speed and compatibility" preference Bug: 356898105 Flag: EXEMPT bugfix Test: Manual testing atest -c WifiTetherSettingsTest Change-Id: I734959ed511929bfcc9a314770a4acdf9a7e0b99 --- .../wifi/tether/WifiTetherSettings.java | 18 +- .../wifi/tether/WifiTetherSettingsTest.java | 187 +++++++++++------- 2 files changed, 127 insertions(+), 78 deletions(-) diff --git a/src/com/android/settings/wifi/tether/WifiTetherSettings.java b/src/com/android/settings/wifi/tether/WifiTetherSettings.java index 74671b5f4af..980dee57753 100644 --- a/src/com/android/settings/wifi/tether/WifiTetherSettings.java +++ b/src/com/android/settings/wifi/tether/WifiTetherSettings.java @@ -371,12 +371,20 @@ public class WifiTetherSettings extends RestrictedDashboardFragment || !mWifiRestriction.isHotspotAvailable(context)) { keys.add(KEY_WIFI_TETHER_NETWORK_NAME); keys.add(KEY_WIFI_TETHER_SECURITY); + keys.add(KEY_WIFI_HOTSPOT_SECURITY); keys.add(KEY_WIFI_TETHER_NETWORK_PASSWORD); keys.add(KEY_WIFI_TETHER_AUTO_OFF); keys.add(KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); + keys.add(KEY_WIFI_HOTSPOT_SPEED); keys.add(KEY_INSTANT_HOTSPOT); - } else if (!mIsInstantHotspotEnabled) { - keys.add(KEY_INSTANT_HOTSPOT); + } else { + if (!isSpeedFeatureAvailable()) { + keys.add(KEY_WIFI_HOTSPOT_SECURITY); + keys.add(KEY_WIFI_HOTSPOT_SPEED); + } + if (!mIsInstantHotspotEnabled) { + keys.add(KEY_INSTANT_HOTSPOT); + } } // Remove duplicate @@ -400,6 +408,12 @@ public class WifiTetherSettings extends RestrictedDashboardFragment public List createPreferenceControllers(Context context) { return buildPreferenceControllers(context, null /* listener */); } + + @VisibleForTesting + boolean isSpeedFeatureAvailable() { + return FeatureFactory.getFeatureFactory().getWifiFeatureProvider() + .getWifiHotspotRepository().isSpeedFeatureAvailable(); + } } @VisibleForTesting diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java index 299d545c49b..0bc0a32daa6 100644 --- a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java +++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java @@ -263,91 +263,114 @@ public class WifiTetherSettingsTest { @Test public void getNonIndexableKeys_tetherAvailable_keysNotReturned() { - when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(true); - when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(true); - WifiTetherSettings.SearchIndexProvider searchIndexProvider = - new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction, - true /* isInstantHotspotEnabled */); + WifiTetherSettings.SearchIndexProvider searchIndexProvider = createSearchIndexProvider( + true /* isTetherAvailable */, true /* isHotspotAvailable */, + true /* isInstantHotspotEnabled */, true /* isSpeedFeatureAvailable */); final List keys = searchIndexProvider.getNonIndexableKeys(mContext); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_NAME); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_SECURITY); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_HOTSPOT_SECURITY); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_HOTSPOT_SPEED); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_INSTANT_HOTSPOT); + } + + @Test + public void getNonIndexableKeys_tetherNotAvailable_keysReturned() { + WifiTetherSettings.SearchIndexProvider searchIndexProvider = createSearchIndexProvider( + false /* isTetherAvailable */, true /* isHotspotAvailable */, + true /* isInstantHotspotEnabled */, true /* isSpeedFeatureAvailable */); + + final List keys = searchIndexProvider.getNonIndexableKeys(mContext); + + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_NAME); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_SECURITY); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_HOTSPOT_SECURITY); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_HOTSPOT_SPEED); + assertThat(keys).contains(WifiTetherSettings.KEY_INSTANT_HOTSPOT); + } + + @Test + public void getNonIndexableKeys_hotspotNotAvailable_keysReturned() { + WifiTetherSettings.SearchIndexProvider searchIndexProvider = createSearchIndexProvider( + true /* isTetherAvailable */, false /* isHotspotAvailable */, + true /* isInstantHotspotEnabled */, true /* isSpeedFeatureAvailable */); + + final List keys = searchIndexProvider.getNonIndexableKeys(mContext); + + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_NAME); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_SECURITY); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_HOTSPOT_SECURITY); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_HOTSPOT_SPEED); + assertThat(keys).contains(WifiTetherSettings.KEY_INSTANT_HOTSPOT); + } + + @Test + public void getNonIndexableKeys_tetherAndHotspotNotAvailable_keysReturned() { + WifiTetherSettings.SearchIndexProvider searchIndexProvider = createSearchIndexProvider( + false /* isTetherAvailable */, false /* isHotspotAvailable */, + true /* isInstantHotspotEnabled */, true /* isSpeedFeatureAvailable */); + + final List keys = searchIndexProvider.getNonIndexableKeys(mContext); + + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_NAME); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_SECURITY); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_HOTSPOT_SECURITY); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_HOTSPOT_SPEED); + assertThat(keys).contains(WifiTetherSettings.KEY_INSTANT_HOTSPOT); + } + + @Test + public void getNonIndexableKeys_instantHotspotNotAvailableOnly_keysContainInstantHotspotOnly() { + WifiTetherSettings.SearchIndexProvider searchIndexProvider = createSearchIndexProvider( + true /* isTetherAvailable */, true /* isHotspotAvailable */, + false /* isInstantHotspotEnabled */, true /* isSpeedFeatureAvailable */); + + final List keys = searchIndexProvider.getNonIndexableKeys(mContext); + + // doesNotContain + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_NAME); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_SECURITY); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_HOTSPOT_SECURITY); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); + assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_HOTSPOT_SPEED); + // contains + assertThat(keys).contains(WifiTetherSettings.KEY_INSTANT_HOTSPOT); + } + + @Test + public void getNonIndexableKeys_speedFeatureNotAvailableOnly_keysContainInstantHotspotOnly() { + WifiTetherSettings.SearchIndexProvider searchIndexProvider = createSearchIndexProvider( + true /* isTetherAvailable */, true /* isHotspotAvailable */, + true /* isInstantHotspotEnabled */, false /* isSpeedFeatureAvailable */); + + final List keys = searchIndexProvider.getNonIndexableKeys(mContext); + + // doesNotContain assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_NAME); assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_SECURITY); assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); assertThat(keys).doesNotContain(WifiTetherSettings.KEY_INSTANT_HOTSPOT); - } - - @Test - public void getNonIndexableKeys_tetherNotAvailable_keysReturned() { - when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(false); - when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(true); - WifiTetherSettings.SearchIndexProvider searchIndexProvider = - new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction, - true /* isInstantHotspotEnabled */); - - final List keys = searchIndexProvider.getNonIndexableKeys(mContext); - - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_NAME); - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_SECURITY); - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); - assertThat(keys).contains(WifiTetherSettings.KEY_INSTANT_HOTSPOT); - } - - @Test - public void getNonIndexableKeys_hotspotNotAvailable_keysReturned() { - when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(true); - when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(false); - WifiTetherSettings.SearchIndexProvider searchIndexProvider = - new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction, - true /* isInstantHotspotEnabled */); - - final List keys = searchIndexProvider.getNonIndexableKeys(mContext); - - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_NAME); - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_SECURITY); - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); - assertThat(keys).contains(WifiTetherSettings.KEY_INSTANT_HOTSPOT); - } - - @Test - public void getNonIndexableKeys_tetherAndHotspotNotAvailable_keysReturned() { - when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(false); - when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(false); - WifiTetherSettings.SearchIndexProvider searchIndexProvider = - new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction, - true /* isInstantHotspotEnabled */); - - final List keys = searchIndexProvider.getNonIndexableKeys(mContext); - - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_NAME); - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_SECURITY); - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); - assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); - } - - @Test - public void getNonIndexableKeys_instantHotspotNotAvailableOnly_keysContainInstantHotspotOnly() { - when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(true); - when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(true); - WifiTetherSettings.SearchIndexProvider searchIndexProvider = - new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction, - false /* isInstantHotspotEnabled */); - - final List keys = searchIndexProvider.getNonIndexableKeys(mContext); - - assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_NAME); - assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_SECURITY); - assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_NETWORK_PASSWORD); - assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_AUTO_OFF); - assertThat(keys).doesNotContain(WifiTetherSettings.KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY); - assertThat(keys).contains(WifiTetherSettings.KEY_INSTANT_HOTSPOT); + // contains + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_HOTSPOT_SECURITY); + assertThat(keys).contains(WifiTetherSettings.KEY_WIFI_HOTSPOT_SPEED); } @Test @@ -506,6 +529,18 @@ public class WifiTetherSettingsTest { mSettings.onCreate(Bundle.EMPTY); } + private WifiTetherSettings.SearchIndexProvider createSearchIndexProvider( + boolean isTetherAvailable, boolean isHotspotAvailable, boolean isInstantHotspotEnabled, + boolean isSpeedFeatureAvailable) { + when(mWifiRestriction.isTetherAvailable(mContext)).thenReturn(isTetherAvailable); + when(mWifiRestriction.isHotspotAvailable(mContext)).thenReturn(isHotspotAvailable); + WifiTetherSettings.SearchIndexProvider provider = + spy(new WifiTetherSettings.SearchIndexProvider(XML_RES, mWifiRestriction, + isInstantHotspotEnabled)); + when(provider.isSpeedFeatureAvailable()).thenReturn(isSpeedFeatureAvailable); + return provider; + } + @Implements(RestrictedDashboardFragment.class) public static final class ShadowRestrictedDashboardFragment { From c2ed6d324238ff4e78ea258a56f9f298e5129e31 Mon Sep 17 00:00:00 2001 From: Haijie Hong Date: Sun, 11 Aug 2024 18:40:14 +0800 Subject: [PATCH 6/9] Move rename button next to the device name BUG: 343317785 Test: atest GeneralBluetoothDetailsHeaderControllerTest Flag: com.android.settings.flags.enable_bluetooth_device_details_polish Change-Id: I87f030ca48d3edac13759fe51499b7e400dbb795 --- res/layout/advanced_bt_entity_header.xml | 41 +++- res/layout/general_bt_entity_header.xml | 80 ++++++++ res/layout/le_audio_bt_entity_header.xml | 42 +++- res/xml/bluetooth_device_details_fragment.xml | 7 + ...ancedBluetoothDetailsHeaderController.java | 16 +- .../BluetoothDetailsHeaderController.java | 4 + .../BluetoothDeviceDetailsFragment.java | 10 +- ...neralBluetoothDetailsHeaderController.java | 108 +++++++++++ ...AudioBluetoothDetailsHeaderController.java | 15 +- ...dBluetoothDetailsHeaderControllerTest.java | 24 ++- ...lBluetoothDetailsHeaderControllerTest.java | 180 ++++++++++++++++++ 11 files changed, 507 insertions(+), 20 deletions(-) create mode 100644 res/layout/general_bt_entity_header.xml create mode 100644 src/com/android/settings/bluetooth/GeneralBluetoothDetailsHeaderController.java create mode 100644 tests/robotests/src/com/android/settings/bluetooth/GeneralBluetoothDetailsHeaderControllerTest.java diff --git a/res/layout/advanced_bt_entity_header.xml b/res/layout/advanced_bt_entity_header.xml index 833f6bda19d..37ae843f514 100644 --- a/res/layout/advanced_bt_entity_header.xml +++ b/res/layout/advanced_bt_entity_header.xml @@ -17,6 +17,7 @@ - + android:gravity="center"> + + + + + + + + + + + + + + + + + diff --git a/res/layout/le_audio_bt_entity_header.xml b/res/layout/le_audio_bt_entity_header.xml index 460ae69edd2..81911e9c946 100644 --- a/res/layout/le_audio_bt_entity_header.xml +++ b/res/layout/le_audio_bt_entity_header.xml @@ -17,6 +17,7 @@ - + android:gravity="center"> + + + + + { + RemoteDeviceNameDialogFragment.newInstance(mCachedDevice).show( + mFragment.getFragmentManager(), RemoteDeviceNameDialogFragment.TAG); + }); + } } } diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java index 462f422a56c..3fbd445c8fc 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java @@ -26,6 +26,7 @@ import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceScreen; import com.android.settings.R; +import com.android.settings.flags.Flags; import com.android.settings.widget.EntityHeaderController; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; @@ -47,6 +48,9 @@ public class BluetoothDetailsHeaderController extends BluetoothDetailsController @Override public boolean isAvailable() { + if (Flags.enableBluetoothDeviceDetailsPolish()) { + return false; + } boolean hasLeAudio = mCachedDevice.getUiAccessibleProfiles() .stream() .anyMatch(profile -> profile.getProfileId() == BluetoothProfile.LE_AUDIO); diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java index 5f9957b9121..ccf38ed2835 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java @@ -50,6 +50,7 @@ import com.android.settings.R; import com.android.settings.connecteddevice.stylus.StylusDevicesController; import com.android.settings.core.SettingsUIDeviceConfig; import com.android.settings.dashboard.RestrictedDashboardFragment; +import com.android.settings.flags.Flags; import com.android.settings.inputmethod.KeyboardSettingsPreferenceController; import com.android.settings.overlay.FeatureFactory; import com.android.settings.slices.SlicePreferenceController; @@ -213,8 +214,8 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment finish(); return; } - use(AdvancedBluetoothDetailsHeaderController.class).init(mCachedDevice); - use(LeAudioBluetoothDetailsHeaderController.class).init(mCachedDevice, mManager); + use(AdvancedBluetoothDetailsHeaderController.class).init(mCachedDevice, this); + use(LeAudioBluetoothDetailsHeaderController.class).init(mCachedDevice, mManager, this); use(KeyboardSettingsPreferenceController.class).init(mCachedDevice); final BluetoothFeatureProvider featureProvider = @@ -338,7 +339,7 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if (!mUserManager.isGuestUser()) { + if (!Flags.enableBluetoothDeviceDetailsPolish() && !mUserManager.isGuestUser()) { MenuItem item = menu.add(0, EDIT_DEVICE_NAME_ITEM_ID, 0, R.string.bluetooth_rename_button); item.setIcon(com.android.internal.R.drawable.ic_mode_edit); @@ -365,6 +366,9 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment Lifecycle lifecycle = getSettingsLifecycle(); controllers.add(new BluetoothDetailsHeaderController(context, this, mCachedDevice, lifecycle)); + controllers.add( + new GeneralBluetoothDetailsHeaderController( + context, this, mCachedDevice, lifecycle)); controllers.add(new BluetoothDetailsButtonsController(context, this, mCachedDevice, lifecycle)); controllers.add(new BluetoothDetailsCompanionAppsController(context, this, diff --git a/src/com/android/settings/bluetooth/GeneralBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/GeneralBluetoothDetailsHeaderController.java new file mode 100644 index 00000000000..57a10278190 --- /dev/null +++ b/src/com/android/settings/bluetooth/GeneralBluetoothDetailsHeaderController.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.util.Pair; +import android.view.View; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.flags.Flags; +import com.android.settingslib.bluetooth.BluetoothUtils; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.widget.LayoutPreference; + +/** This class adds a header with device name and status (connected/disconnected, etc.). */ +public class GeneralBluetoothDetailsHeaderController extends BluetoothDetailsController { + private static final String KEY_GENERAL_DEVICE_HEADER = "general_bluetooth_device_header"; + + @Nullable + private LayoutPreference mLayoutPreference; + + public GeneralBluetoothDetailsHeaderController( + Context context, + PreferenceFragmentCompat fragment, + CachedBluetoothDevice device, + Lifecycle lifecycle) { + super(context, fragment, device, lifecycle); + } + + @Override + public boolean isAvailable() { + if (!Flags.enableBluetoothDeviceDetailsPolish()) { + return false; + } + boolean hasLeAudio = + mCachedDevice.getUiAccessibleProfiles().stream() + .anyMatch(profile -> profile.getProfileId() == BluetoothProfile.LE_AUDIO); + return !BluetoothUtils.isAdvancedDetailsHeader(mCachedDevice.getDevice()) && !hasLeAudio; + } + + @Override + protected void init(PreferenceScreen screen) { + mLayoutPreference = screen.findPreference(KEY_GENERAL_DEVICE_HEADER); + } + + @Override + protected void refresh() { + if (!isAvailable() || mLayoutPreference == null) { + return; + } + ImageView imageView = mLayoutPreference.findViewById(R.id.bt_header_icon); + if (imageView != null) { + final Pair pair = + BluetoothUtils.getBtRainbowDrawableWithDescription(mContext, mCachedDevice); + imageView.setImageDrawable(pair.first); + imageView.setContentDescription(pair.second); + } + + TextView title = mLayoutPreference.findViewById(R.id.bt_header_device_name); + if (title != null) { + title.setText(mCachedDevice.getName()); + } + TextView summary = mLayoutPreference.findViewById(R.id.bt_header_connection_summary); + if (summary != null) { + summary.setText(mCachedDevice.getConnectionSummary()); + } + ImageButton renameButton = mLayoutPreference.findViewById(R.id.rename_button); + renameButton.setVisibility(View.VISIBLE); + renameButton.setOnClickListener( + view -> { + RemoteDeviceNameDialogFragment.newInstance(mCachedDevice) + .show( + mFragment.getFragmentManager(), + RemoteDeviceNameDialogFragment.TAG); + }); + } + + @Override + @NonNull + public String getPreferenceKey() { + return KEY_GENERAL_DEVICE_HEADER; + } +} diff --git a/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java index a5e9cde2d17..25248942be7 100644 --- a/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java +++ b/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java @@ -27,14 +27,17 @@ import android.os.Looper; import android.util.Log; import android.util.Pair; import android.view.View; +import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; +import com.android.settings.flags.Flags; import com.android.settings.fuelgauge.BatteryMeterView; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; @@ -86,6 +89,7 @@ public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceContr @VisibleForTesting static final int INVALID_RESOURCE_ID = -1; + PreferenceFragmentCompat mFragment; @VisibleForTesting LayoutPreference mLayoutPreference; LocalBluetoothManager mManager; @@ -151,11 +155,12 @@ public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceContr } public void init(CachedBluetoothDevice cachedBluetoothDevice, - LocalBluetoothManager bluetoothManager) { + LocalBluetoothManager bluetoothManager, PreferenceFragmentCompat fragment) { mCachedDevice = cachedBluetoothDevice; mManager = bluetoothManager; mProfileManager = bluetoothManager.getProfileManager(); mCachedDeviceGroup = Utils.findAllCachedBluetoothDevicesByGroupId(mManager, mCachedDevice); + mFragment = fragment; } @VisibleForTesting @@ -163,6 +168,14 @@ public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceContr if (mLayoutPreference == null || mCachedDevice == null) { return; } + if (Flags.enableBluetoothDeviceDetailsPolish()) { + ImageButton renameButton = mLayoutPreference.findViewById(R.id.rename_button); + renameButton.setVisibility(View.VISIBLE); + renameButton.setOnClickListener(view -> { + RemoteDeviceNameDialogFragment.newInstance(mCachedDevice).show( + mFragment.getFragmentManager(), RemoteDeviceNameDialogFragment.TAG); + }); + } final ImageView imageView = mLayoutPreference.findViewById(R.id.entity_header_icon); if (imageView != null) { final Pair pair = diff --git a/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java index 8d96f213446..af4888b3588 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java @@ -28,15 +28,19 @@ import android.bluetooth.BluetoothDevice; import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; +import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.view.LayoutInflater; import android.view.View; +import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; +import androidx.preference.PreferenceFragmentCompat; + import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.core.BasePreferenceController; @@ -93,6 +97,8 @@ public class AdvancedBluetoothDetailsHeaderControllerTest { private CachedBluetoothDevice mCachedDevice; @Mock private BluetoothAdapter mBluetoothAdapter; + @Mock + private PreferenceFragmentCompat mFragment; private AdvancedBluetoothDetailsHeaderController mController; private LayoutPreference mLayoutPreference; @@ -103,7 +109,7 @@ public class AdvancedBluetoothDetailsHeaderControllerTest { mContext = Robolectric.buildActivity(SettingsActivity.class).get(); mController = new AdvancedBluetoothDetailsHeaderController(mContext, "pref_Key"); when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice); - mController.init(mCachedDevice); + mController.init(mCachedDevice, mFragment); mLayoutPreference = new LayoutPreference(mContext, LayoutInflater.from(mContext).inflate(R.layout.advanced_bt_entity_header, null)); mController.mLayoutPreference = mLayoutPreference; @@ -540,6 +546,22 @@ public class AdvancedBluetoothDetailsHeaderControllerTest { rightBatteryPrediction); } + @Test + @EnableFlags(Flags.FLAG_ENABLE_BLUETOOTH_DEVICE_DETAILS_POLISH) + public void enablePolishFlag_renameButtonShown() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI, + SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "true", true); + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) + .thenReturn("true".getBytes()); + Set cacheBluetoothDevices = new HashSet<>(); + when(mCachedDevice.getMemberDevice()).thenReturn(cacheBluetoothDevices); + + mController.onStart(); + + ImageButton button = mLayoutPreference.findViewById(R.id.rename_button); + assertThat(button.getVisibility()).isEqualTo(View.VISIBLE); + } + private void assertBatteryPredictionVisible(LinearLayout linearLayout, int visible) { final TextView textView = linearLayout.findViewById(R.id.bt_battery_prediction); assertThat(textView.getVisibility()).isEqualTo(visible); diff --git a/tests/robotests/src/com/android/settings/bluetooth/GeneralBluetoothDetailsHeaderControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/GeneralBluetoothDetailsHeaderControllerTest.java new file mode 100644 index 00000000000..d608f3fcf68 --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/GeneralBluetoothDetailsHeaderControllerTest.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.widget.TextView; + +import com.android.settings.R; +import com.android.settings.core.SettingsUIDeviceConfig; +import com.android.settings.flags.Flags; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.shadow.ShadowDeviceConfig; +import com.android.settings.testutils.shadow.ShadowEntityHeaderController; +import com.android.settingslib.bluetooth.LeAudioProfile; +import com.android.settingslib.widget.LayoutPreference; + +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowDeviceConfig.class}) +public class GeneralBluetoothDetailsHeaderControllerTest + extends BluetoothDetailsControllerTestBase { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private GeneralBluetoothDetailsHeaderController mController; + private LayoutPreference mPreference; + + @Mock private BluetoothDevice mBluetoothDevice; + @Mock private LeAudioProfile mLeAudioProfile; + + @Override + public void setUp() { + super.setUp(); + FakeFeatureFactory.setupForTest(); + android.provider.DeviceConfig.setProperty( + android.provider.DeviceConfig.NAMESPACE_SETTINGS_UI, + SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, + "true", + true); + mController = + new GeneralBluetoothDetailsHeaderController( + mContext, mFragment, mCachedDevice, mLifecycle); + mPreference = new LayoutPreference(mContext, R.layout.general_bt_entity_header); + mPreference.setKey(mController.getPreferenceKey()); + mScreen.addPreference(mPreference); + setupDevice(mDeviceConfig); + when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice); + when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO); + } + + @After + public void tearDown() { + ShadowEntityHeaderController.reset(); + } + + /** + * Test to verify the current test context object works so that we are not checking null against + * null + */ + @Test + public void testContextMock() { + assertThat(mContext.getString(com.android.settingslib.R.string.bluetooth_connected)) + .isNotNull(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_BLUETOOTH_DEVICE_DETAILS_POLISH) + public void header() { + when(mCachedDevice.getName()).thenReturn("device name"); + when(mCachedDevice.getConnectionSummary()).thenReturn("Active"); + + showScreen(mController); + + TextView deviceName = mPreference.findViewById(R.id.bt_header_device_name); + TextView summary = mPreference.findViewById(R.id.bt_header_connection_summary); + assertThat(deviceName.getText().toString()).isEqualTo("device name"); + assertThat(summary.getText().toString()).isEqualTo("Active"); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_BLUETOOTH_DEVICE_DETAILS_POLISH) + public void connectionStatusChangesWhileScreenOpen() { + TextView summary = mPreference.findViewById(R.id.bt_header_connection_summary); + when(mCachedDevice.getConnectionSummary()) + .thenReturn( + mContext.getString(com.android.settingslib.R.string.bluetooth_connected)); + + showScreen(mController); + String summaryText1 = summary.getText().toString(); + when(mCachedDevice.getConnectionSummary()).thenReturn(null); + mController.onDeviceAttributesChanged(); + String summaryText2 = summary.getText().toString(); + when(mCachedDevice.getConnectionSummary()) + .thenReturn( + mContext.getString(com.android.settingslib.R.string.bluetooth_connecting)); + mController.onDeviceAttributesChanged(); + String summaryText3 = summary.getText().toString(); + + assertThat(summaryText1) + .isEqualTo( + mContext.getString(com.android.settingslib.R.string.bluetooth_connected)); + assertThat(summaryText2).isEqualTo(""); + assertThat(summaryText3) + .isEqualTo( + mContext.getString(com.android.settingslib.R.string.bluetooth_connecting)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_BLUETOOTH_DEVICE_DETAILS_POLISH) + public void isAvailable_untetheredHeadset_returnFalse() { + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) + .thenReturn("true".getBytes()); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_BLUETOOTH_DEVICE_DETAILS_POLISH) + public void isAvailable_notUntetheredHeadset_returnTrue() { + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) + .thenReturn("false".getBytes()); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_BLUETOOTH_DEVICE_DETAILS_POLISH) + public void isAvailable_leAudioDevice_returnFalse() { + when(mCachedDevice.getUiAccessibleProfiles()) + .thenReturn(List.of(mLeAudioProfile)); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_BLUETOOTH_DEVICE_DETAILS_POLISH) + public void isAvailable_flagEnabled_returnTrue() { + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) + .thenReturn("false".getBytes()); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_BLUETOOTH_DEVICE_DETAILS_POLISH) + public void iaAvailable_flagDisabled_returnFalse() { + assertThat(mController.isAvailable()).isFalse(); + } +} From b213804b87c49e467fa8d67f72a4782f503ef345 Mon Sep 17 00:00:00 2001 From: tomhsu Date: Mon, 12 Aug 2024 05:24:38 +0000 Subject: [PATCH 7/9] Fix crash due to UserManager is null. - Instance of UserManager get from onCraete, so we can not use this onAttach. Flag: EXEMPT bug fix Fix: 289232540 Test: Manual test Change-Id: Ieb62d8289db0dbcd5a16280aa66f3b3de8936e26 --- .../network/telephony/MobileNetworkSettings.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java index acf674f3f7c..a5cdb954664 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java +++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java @@ -202,12 +202,6 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme @Override public void onAttach(Context context) { super.onAttach(context); - if (isUiRestricted()) { - Log.d(LOG_TAG, "Mobile network page is disallowed."); - finish(); - return; - } - if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { Log.d(LOG_TAG, "Invalid subId, get the default subscription to show."); SubscriptionInfo info = SubscriptionUtil.getSubscriptionOrDefault(context, mSubId); @@ -346,6 +340,11 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme setTelephonyAvailabilityStatus(getPreferenceControllersAsList()); super.onCreate(icicle); + if (isUiRestricted()) { + Log.d(LOG_TAG, "Mobile network page is disallowed."); + finish(); + return; + } final Context context = getContext(); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); mTelephonyManager = context.getSystemService(TelephonyManager.class) From a274bcb400b65243e7ccaa93e1709146ac521eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Mon, 12 Aug 2024 15:32:44 +0200 Subject: [PATCH 8/9] Modes icon picker: measuring improvements Ensure that horizontal spacing between items in the icon picker is distributed to the left and right of the row instead of between the items. This makes the horizontal and vertical spacing the same. Bug: 359171199 Test: manual + hsv Flag: android.app.modes_ui Change-Id: I68c3860ba4092bb6fde2a1218b3f6a95d0c373da --- res/layout/modes_icon_list.xml | 2 +- res/layout/modes_icon_list_item.xml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/res/layout/modes_icon_list.xml b/res/layout/modes_icon_list.xml index 87e647eb7a6..f6f220262ff 100644 --- a/res/layout/modes_icon_list.xml +++ b/res/layout/modes_icon_list.xml @@ -24,7 +24,7 @@ - Date: Mon, 12 Aug 2024 18:04:43 +0200 Subject: [PATCH 9/9] Reduce the size of individual icons in the icon picker Bug: 359171199 Test: manual Flag: android.app.modes_ui Change-Id: Iba5c09948437887f12b946aa39f7c811437f20dc --- res/values/dimens.xml | 6 +++--- .../android/settings/notification/modes/IconUtil.java | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 2024a4559a0..3a327c98a12 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -510,9 +510,9 @@ 16sp 90dp 48dp - 96dp - 56dp - 32dp + 76dp + 52dp + 28dp 32dp 20dp diff --git a/src/com/android/settings/notification/modes/IconUtil.java b/src/com/android/settings/notification/modes/IconUtil.java index 55abccf6231..43161ce085d 100644 --- a/src/com/android/settings/notification/modes/IconUtil.java +++ b/src/com/android/settings/notification/modes/IconUtil.java @@ -82,8 +82,8 @@ class IconUtil { } /** - * Returns a variant of the supplied {@code icon} to be used as the header in the icon picker. - * The inner icon is 48x48dp and it's contained in a circle of diameter 90dp. + * Returns a variant of the supplied {@code icon} to be used as the header in the icon picker + * (large icon within large circle, with the "material secondary" color combination). */ static Drawable makeIconPickerHeader(@NonNull Context context, Drawable icon) { return composeIconCircle( @@ -99,9 +99,9 @@ class IconUtil { } /** - * Returns a variant of the supplied {@code icon} to be used as an option in the icon picker. - * The inner icon is 36x36dp and it's contained in a circle of diameter 54dp. It's also set up - * so that selection and pressed states are represented in the color. + * Returns a variant of the supplied {@code icon} to be used as an option in the icon picker + * (small icon in small circle, with "material secondary" colors for the normal state and + * "material primary" colors for the selected state). */ static Drawable makeIconPickerItem(@NonNull Context context, @DrawableRes int iconResId) { return composeIconCircle(